Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Pimcd2013 PDF
Pimcd2013 PDF
Publicación 1.0
1. Introducción 3
1.1. Precedentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2. ¿Por qué Python? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3. Python vs. Matlab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4. Python en el ámbito científico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2. Puesta en Marcha 9
2.1. Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2. Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3. Consolas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4. IDEs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.5. Pythonpath . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.6. Ayudas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.7. Autoejecución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.8. Cabecera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.9. Generación de ayudas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.10. Carga de Módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.11. Definición de funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.12. Código ejecutable: main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.13. Sistema de verificación del código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3. Lenguajes de Programación 29
3.1. Características de programación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4. Módulos Científicos 37
4.1. Numpy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.2. Scipy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.3. Matplotlib . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6. Polarización 81
6.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
6.2. Programa interactivo para el análisis de la polarización . . . . . . . . . . . . . . . . . . . . . . . 82
6.3. Definiciones de polarizadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
6.4. Definiciones de estados de luz polarizada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
6.5. Cálculo de la intensidad de un vector de polarización . . . . . . . . . . . . . . . . . . . . . . . . 86
6.6. Polarización básica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
6.7. Ley de Malus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
6.8. Efecto Zenon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
I
7. Interacción radiación-materia 91
7.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
7.2. Dipolo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
7.3. Resonancia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
7.4. Densidad espectral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
7.5. Sección eficaz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
II
14.10. Propiedades de la transformada de Fourier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
III
IV
PIMCD2013-python, Publicación 1.0
Índice general 1
PIMCD2013-python, Publicación 1.0
2 Índice general
CAPÍTULO 1
Introducción
1.1 Precedentes
El manejo de aplicaciones informáticas, actualmente generalizado, no conlleva realmente el dominio de las nuevas
tecnologías informáticas. La programación y simulación se ha convertido en una tarea necesaria en la sociedad
y en el ámbito científico-técnico, y por supuesto en el ámbito académico. Sin embargo, la gran mayoría de los
alumnos que llegan a los primeros cursos universitarios no tienen ninguna experiencia en programación. Incluso en
cursos superiores, un porcentaje demasiado alto no han ido más allá de los ejercicios propuestos en las asignaturas
obligatorías de cálculo numérico.
Dentro de la asignatura “Óptica” del grado en Física de la Universidad Complutense de Madrid, Luis Miguel
Sánchez Brea y José María Herrera Fernández (profesor de la asignatura y alumno-tutor respectivamente) in-
corporaron en el curso 2011 - 2012 una serie de seminarios de “Computación científica de fenómenos ópticos
basados en el lenguaje de programación Python. Los seminarios se enfocaron como apoyo al aprendizaje de Óp-
tica utilizando la programación como medio de resolución de ejercicios en Óptica y fueron la semilla para este
proyecto docente donde pretendemos presentar ejemplos sencillos y de fácil comprensión del uso de Python como
herramienta en diferentes ámbitos.
Python es un lenguaje de programación moderno, de alto nivel, ideal para pequeñas aplicaciones así como para
proyectos de gran envergadura. El código de Python es similar al pseudocódigo utilizado para esquematizar la
programación. Permite realizar todo lo necesario para una programación completa: diseño orientado a objetos,
unidades de testeo, generación de documentación en el código, sencilla interacción con el sistema operativo, etc.
Es compatible con la programación basada en objetos, así como la estructurada y por eventos. Tiene un excelente
conjunto de librerías para extender su funcionalidad, incluyendo librerías científicas que permiten realizar nume-
rosas tareas de tratamiento de datos, visualización, cálculo numérico y simbólico y otras aplicaciones específicas.
Además, existe una comunidad muy grande de usuarios que, debido a la filosofía “open source”, son mucho más
proclives a compartir su código.
A la hora de empezar a trabajar en computación lo primero que nos sorprende es la gran cantidad de lenguajes
de programación, por lo que se debe realizar una selección cuidadosa. Una lista de los lenguajes de programación
3
PIMCD2013-python, Publicación 1.0
Figura 1.1: Figura 1. Lenguajes de programación más utilizados en el mundo. Python se mantiene es-
table como tercer lenguaje de programación después de la familia de C y de Java. Además com-
parándo con un lenguaje de ámbito científico como MATLAB, su posición es notablemente superior
(4,3 % de uso frente a 0,6 %). Fuente: TIOBE Programming Community Index for November 2013,
http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html .
Por un lado existen lenguajes de uso muy extendido. Actualmente Java es el lenguaje más utilizado, pues se
utiliza para aplicaciones corporativas. El segundo lenguaje es C en sus diferentes versiones, un estándar en la
programación durante muchísimos años. La mayor parte de las más famosas aplicaciones de escritorio están
escritas en C++. Estos dos lenguajes dominan el negocio de la programación. PHP domina en la red. Mientras Java
lo utilizan las grandes organizaciones, PHP lo utilizan empresas más pequeñas e individuos para crear aplicaciones
web dinámicas. Por otro lado, hay numeros paquetes numéricos como Matlab, Mathematica y Maple que se
utilizan en centros de investigación.
La gran mayoría de los alumnos que llegan a los primeros cursos de Física no han programado nada o casi nada.
Es por ello que hemos elegido un lenguaje de programación lo más sencillo y completo posible. Python nos
ofrece una gran sencillez de programación, una gran cantidad de librerías en las que basar nuestros programas y
4 Capítulo 1. Introducción
PIMCD2013-python, Publicación 1.0
una importante comunidad de usuarios y desarrolladores que lo soportan. También tiene la ventaja de ser “open
source”, por lo no nos vamos a tener que asumir ningún coste debido a derechos de autor. Otra ventaja es ser un
sistema multiplataforma, así que podremos programar en cualquier sistema operativo como Windows, Linux o
Mac Os.
Veamos resumidas algunas de las ventajas del lenguaje de programación Python.
Python es un lenguaje de programación de alto nivel que permite una rápida implementación de aplicaciones
con muy poco código. Es ideal para prototipado rápido, pero tambien permite el desarrollo de grandes
aplicaciones.
El código de Python es legible, muy similar al pseudocódigo utilizado para esquematizar la programación.
Python es un lenguaje interpretado por una máquina virtual, no compilado, por lo que es multiplataforma.
Python permite realizar todo lo necesario para una programación de código moderna: diseño orientado a
objetos, unidades de testeo, generación de documentación en el código, sencilla interacción con el sistema
operativo, etc.
Tiene estructuras de datos de alto nivel, como las listas, tuplas y diccionarios
Permite la programación basada en objetos, aunque tambien soporta la programación funcional y la progra-
mación estructurada.
Es un lenguaje dinámico, pudiendose añadir nuevas funciones y clases a un objeto existente, incluso en
tiempo de ejecución.
Existen librerías científicas escritas en Phython maduras y con gran capacidad.
Permite extender trivialmente su aplicabilidad con módulos escritos en C, C++ y Fortran, de los que existen
numerosas librerías científicas. Al ser los procedimientos más rápidos y eficientes para desarrollo de algo-
ritmos matemáticos (aunque a veces mucho más complicados para otras tareas), se pueden programar las
partes más sensibles en C y realizar el resto de la aplicación en Python
Tiene un enorme conjunto de librerías para extender su funcionalidad: para acceseo a base de datos, para
almacenamiento de datos, para procesamiento de imágenes, de sonido, para programación con GUIs (Inter-
faces gráficas de usuario, ventanas), y otras muchisimas más.
Permite el acceso a toda la funcionalidad del sistema operativo (sobre todo en Linux).
Python se puede utilizar como software “glue” para combinar distintas aplicaciones en una sola (sobre todo
el Linux).
El uso de la indentación en lugar de los paréntesis permite una mayor legibilidad del código.
El código Python se escriben en archivos .py. Estos archivos se pueden agrupar en módulos, y se pueden
importar los elementos de los módulos a otros archivos.
Para sistemas Unix, se puede utilizar código para hacer el archivo autoejecutable indicando al sistema que
programa que el es un ejecutable (no hace falta que sea un archivo .exe). utilizar (#!)
El código es extraordinariamente sencillo de comprender.
Existen módulos de gran calidad y repositorios de código donde se puede encontrar numerosas aplicaciones.
En linux, gran parte de los módulos están en los repositorios estándar, por lo que su instalación es tán sencilla
como pulsar un botón.
Existe una comunidad muy grande de usuarios y debido a la filosofía open, los desarrolladores son mucho
más proclives a compartir su código con otros. Además no suelen existir problemas de uso del código de
otros, siempre que cumplas los requisitos de la licencia GLS.
Al ser Python un lenguaje de propósito general, el código que se genera se puede aprovechar de las ventajas
de otros módulos (como comprimir archivos, enviar datos, automatizar procesos, enlazar nuestros datos con
bases de datos, con servidores web....
Python se puede ejecutar en bastantes IDEs (editores), desde algunos muy sencillos, como IDLE, hasta otros
tan completos como Spyder, Eclipse o NetBeans.
Existen diversas técnica para creas sistemas de ventanas (Tkinter, wx, QT, etc.) de una forma sorprendente-
mente sencilla en comparación con otros sistemas operativos. Si se nos da bien el curso, intentaremos ver
QT4, a través de la herramienta de creación de ventanas QT Designer 4.
MATLAB (abreviatura de MATrix LABoratory, “laboratorio de matrices”) es un software matemático que ofrece
un entorno de desarrollo integrado (IDE) con un lenguaje de programación propio (lenguaje M). Está disponible
para las plataformas Unix, Windows y Apple Mac OS X. Entre sus prestaciones básicas se hallan: la manipulación
de matrices, la representación de datos y funciones, la implementación de algoritmos, la creación de interfaces de
usuario (GUI) y la comunicación con programas en otros lenguajes y con otros dispositivos hardware. El paquete
MATLAB dispone de dos herramientas adicionales que expanden sus prestaciones, a saber, Simulink (plataforma
de simulación multidominio) y GUIDE (editor de interfaces de usuario - GUI). Además, se pueden ampliar las
capacidades de MATLAB con las cajas de herramientas (toolboxes); y las de Simulink con los paquetes de bloques
(blocksets). Matlab tiene ventajas muy interesantes para la programación científica.
Las sesiones interactivas permiten “jugar” con los datos de una forma muy sencilla.
La sintaxis está muy bien desarrollada para trabajar con vectores y matrices.
La capacidad de visualización de datos y modificación de figuras es excepcionalemente buena.
Existen librerías (normalmente propietarias) de gran calidad y existe una comunidad muy grande de usua-
rios.
No obstante, la mayor parte de estas ventajas también las tiene Phython. Además, tiene una serie de desventajas
que lo desaconsejan como primer lenguaje de programación.
En primer lugar no es un lenguaje de programación “verdadero” ya que está muy dedicado al cálculo numéri-
co, aunque su funcionalidad se ha extendido con numerosas toolboxes (control de bases de datos, interacción
con el sistema operativo, etc...).
Adicionalmente, es un lenguaje propietario que requiere de licencias excesivamente caras para estudiantes.
No se puede ejecutar código de forma independiente (standalone). NOTA: Recientemente se ha proporcio-
nado una herramienta adicional llamada MATLAB Builder bajo la sección de herramientas “Application
Deployment” para utilizar funciones MATLAB como archivos de biblioteca que pueden ser usados con
ambientes de construcción de aplicación .NET o Java.
Es horrible hacer un código largo, pues cada función debe estar en un único archivo. Esto significan miles
de archivos y hace más complicado que varios programadores trabajen en el mismo código.
También Python tiene algunas desventajas respecto a Matlab:
No es tan sencillo manipular las figuras en tiempo real.
Al existir una cantidad ingente de programadores que aportan su código de forma libre, a veces es dificil
determinar cuales son los módulos “mejores” para una cierta aplicación. Hay más posiblidades donde elegir.
Python es un lenguaje de programación de propósito general que, en un principio, no se pensó para aplicacio-
nes científicas. Con los años, siguiendo la filosofía GNU, se han desarrollado multitud de módulos científicos,
que permiten realizar numerosas tareas de tratamiento de datos, visualización, cálculo simbólico y aplicaciones
científicas específicas. Nosotros utilizaremos fundamentalmente los siguientes módulos científicos:
Numpy: Generación de tipos de datos científicos. http://www.numpy.org/
Scipy: Funciones científicas de uso general. http://www.scipy.org/
Matplotlib: Gráficas en 2D y 3D. http://matplotlib.org/
Mayavi: visualización de datos tridimensionales. http://code.enthought.com/projects/mayavi/
6 Capítulo 1. Introducción
PIMCD2013-python, Publicación 1.0
8 Capítulo 1. Introducción
CAPÍTULO 2
Puesta en Marcha
Como en casi todos los programas, dependiendo del sistema operativo del que disponga nuestro ordenador la
instalación de software variará ligeramente y Python no es una excepción. A fin de que el sistema operativo no
suponga un bache en nuestro camino hacia el aprendizaje de Python, en esta sección veremos la instalación paso a
paso en los dos sistemas operativos más extendidos en la actualidad. Para otros sistemas operativos recomendamos
al lector que en caso de duda acuda a la gran bibliografía existente en la red.
2.1 Windows
A la hora de instalar Python en el sistema operativo de Microsoft tenemos dos opciones según el uso que vayamos
a hacer del lenguaje de programación. Por una parte, como ya hemos mencionado en la introducción, Python es un
leguanje de programación de uso general por lo que, si éste es el caso, recomendamos la instalación del paquete
básico e incorporar los módulos que necesitemos. En el sitio oficial de Phython en la sección de descargas podemos
encontrar diferentes versiones. En la actualidad conviven las versiones 3.3.* y 2.7.* donde * es la última versión
estable. Las instalaciones de este libro fueron realizadas con la versión 2.7.2 aunque no varía sustancialmente
para versiones posteriores. Por otra parte, como la programación que vamos a realizar está orientada al ámbito
científico-académico e ingenieril, la opción que más se ajusta a nuestros intereses es el paquete Python(x,y).
Python(x,y) es un software de desarrollo gratuito que incorpora los principales módulos necesarios para cálculo
numérico junto con algún otro complemento que analizaremos posteriormente. La instalación es similar a otros
programas de Windows. Lo primero que debemos de hacer es descargarmos la última versión del programa desde
la sección de descargas de la web oficial, http://code.google.com/p/pythonxy/wiki/Downloads?tm=2.
9
PIMCD2013-python, Publicación 1.0
En la Figura 2 podemos ver que en el mismo enlace disponemos de las notas de instalación que recomendamos
repasar antes de iniciar el proceso. Son unos 420 MB (ya que tiene bastantes aplicaciones). Una vez instalado
observaremos como nuestros archivos con extensión .py se asocian con python por lo que si pinchamos dos veces
sobre ellos se ejecutarán.
Figura 2.1: Figura 2. Descarga de Python(x,y) desde la sección correspondiente en la web oficial. En las misma
página podemos encontrar las notas de instalación que debemos de tener en cuenta.
2.2 Linux
Python viene incorporado en la mayoría de las distribuciones existentes. Si en una terminal ejecutamos el comando
python - V
comprobaremos que versión de Python tenemos instalada. En caso de no ser así podemos instalarlo ejecutando
apt-get install python
Otra opción es, por ejemplo, desde el centro de software en Ubuntu. Basta con buscar python en el lugar co-
rrespondiente y nos indicará si lo tenemos instalado y si queremos instalarlo o desistalarlo. Para instalar nuevos
módulos se puede seguir el mismo procedimiento ya sea desde la terminal o desde el centro de software.
A la hora de iniciarse en la programación es muy importante conocer la sintasis del lenguaje que se va a utilizar
para desarrollar codigo de una manera correcta. De igual importancia es conocer las herramientas de las que
disponemos para ejecutar y depurar el código desarrollado.
2.3 Consolas
Desde el mismo momento en el que Python se instala en el sistema podemos empezar a programar abriendo una
consola interactiva en una terminal. La consola interactiva más sencilla se ejecuta directamente en un terminal
(Linux) escribiendo
python
Inmediantamente podremos ir introduciendo lineas de comandos y ejecutar de una en una. En Windows la forma
es equivalente.
Existen concolas interactivas más completas que nos van indicando ayudas inmediatas a medida que vamos escri-
biendo código. Se introduce la instruccion y se ejecuta de una en una. Esta forma de programación es adecuada
cuando somos principiantes y estamos desarrollando programas cortos donde nos interesa ver inmediatamente si
el resultado es correcto o no.
2.4 IDEs
Las consolas no es el mejor procedimiento para desarrollar programas extensos. El código escrito en la consola
no es fácil de seguir y de volver a utilizar. Una programación eficaz se realiza mediante un Entorno de Desarro-
llo Integrado (IDE, Integrated Developed Environment). Con este acrónimo inglés se describe a toda aplicación
de sofware que proporciona una facilidad comprensiva a la hora de la creación de programas informáticos sea
en el lenguaje que sea. Normalmente un IDE consta de un editor de código fuente, un compliador y/o un intér-
prete, herramientas de creación y un depurador. También suelen tener consolas interactivas. En esta sección nos
centraremos en tres IDEs en particular: IDLE, Spyder y Eclipse.
IDLE: es el IDE de Python basado en el entorno de ventantas thinter y desarrollado 100 % también en
Python. Es el más simple de los tres que veremos pero para principiantes quizás sea el más adecuado. Posee
modo depuración aunque para programadores experimentados se queda bastante corto. La instalación en
Windows se realiza con la instalación del propio lenguaje mientras que en el resto de SO la instalación se
realiza de la forma usual. Para más información podemos consultar la documentación oficial en el siguiente
enlace: http://docs.python.org/2/library/idle.html. En la Figura 3 se muestra un ejemplo de creación de un
array en la consola de IDLE.
Spyder: Spyder es un IDE específico para Python orientado a científicos y desarrollo ingenieril con una
interfaz muy similar a Matlab, por lo que es la opción más adecuada cuando se quiere migrar de Matlab
a Python. Si hemos instalado Python(x,y), al iniciar el programa se nos abre una ventana con diversas
caracterísitcas. Dentro de la pestaña “Shortcuts” nos encontramos el apartado “Spyder”. Bastará con pinchar
en el símbolo del programa (Figura 4) e inmediatamente se nos abrira el programa. Como siempre, si nos
encontramos en linux abrimos una terminal y ejecutamos la orden
sudo apt-get install spyder
Tanto en linux como en Windows si no los tenemos debemos instalar la ultima versión de Java Development Kit
(JDK) y el Java Runtime Environment (JRE). Para configurar PyDev en Eclipse debemos de seguir los siguientes
pasos:
2.4. IDEs 11
PIMCD2013-python, Publicación 1.0
Figura 2.3: Figura 4. Spyder en Python(X,Y). Para iniciar el programa pinchamos en el símbolo resaltado en la
imagen.
2.4. IDEs 13
PIMCD2013-python, Publicación 1.0
1. Abrimos Eclipse y nos vamos a Help -> Install New Software. En la pantalla emergente añadimos
“http://pydev.org/updates” en el campo work with.
2. Pulsamos en Add, esperamos a que actualize el campo de abajo para poder seleccionar PyDev, luego pulsa-
mos en Next, y seguimos el asistente para completar la instalación.
3. Reiniciamos Eclipse.
4. Ahora deberemos especificar dónde se encuentra el interprete de python en las preferencias Eclipse, para
ello nos vamos a Eclipse -> preferences -> PyDev -> Interpreter –> Python. Pulsamos en auto Config,
marcamos todo (sino lo está) y aceptamos.
En este caso también tenemos a nuestra disposición un manual oficial muy completo en
http://pydev.org/manual_101_root.html. Si hemos realizado todos los pasos correctamente podremos ejecu-
tar programas realizados en python sin ningún problema. En la Figura [figEclipse] podemos ver un ejemplo de la
disposición de las secciones en Eclipse. Estas seccciones son totalmente configurables por lo que dependiendo de
vuestras preferencias la imagen puede o no coincidir con vuestra disposición.
2.5 Pythonpath
Dependiendo el IDE elegido se nos habrá creado una carpeta como directorio de trabajo. Sin embargo en ocasiones
puede interesarnos trabajar con archivos fuera de este directorio. Para ello debemos de añadir la nueva ruta de
nuestros archivos. Esta operación se realiza en Pythonpath.
Spyder:
Tools –> PYTHONPATH manager.
Se busca el directorio donde tenemos los programas.
Eclipse:
2.6 Ayudas
Las comandos de los lenguajes de programación son prácticamente infinitos y, sólo mediante práctica, consegui-
remos aprendernos algunos de ellos. Sin embargo, en la mayoría de los casos debereos acudir a la documentación
oficial para resolver nuestras dudas. Normalmente cada lenguaje de programación tiene algún comando específico
para acceder a la información que nos interese. Además muchos de los IDE facilitan el acceso a esta información
mediante algún acceso directo o alguna ventana permanente. En general los pasos a seguir cuando no sabemos
qué comando utilizar o la sintaxis de su utilización son los siguientes.
Google. El buscador de información en internet por excelencia. En algunas ocasiones no sabremos muy
bien por donde empezar o nos sabremos el comando equivalente en otro lenguaje de programación. Si el
lector ha tenido esa duda seguramente otra persona la haya tenido antes por lo que podremos encontrar la
respuesta sin demasiado esfuerzo.
Mediante el uso de comandos especificos. Como hemos mencionado, cada lenguaje de programación tiene
una serie de comandos para accecer a la información de los comandos acerca de los cuales tengamos dudas.
Por ejemplo si en una consola tecleamos
import numpy as np
help(np.linspace)
2.6. Ayudas 15
PIMCD2013-python, Publicación 1.0
Parameters
----------
start : scalar
The starting value of the sequence.
stop : scalar
The end value of the sequence, unless ‘endpoint‘ is set to False.
In that case, the sequence consists of all but the last of ‘‘num + 1‘‘
evenly spaced samples, so that ‘stop‘ is excluded. Note that the step
size changes when ‘endpoint‘ is False.
num : int, optional
Number of samples to generate. Default is 50.
endpoint : bool, optional
If True, ‘stop‘ is the last sample. Otherwise, it is not included.
Default is True.
retstep : bool, optional
If True, return (‘samples‘, ‘step‘), where ‘step‘ is the spacing
between samples.
Returns
-------
samples : ndarray
There are ‘num‘ equally spaced samples in the closed interval
‘‘[start, stop]‘‘ or the half-open interval ‘‘[start, stop)‘‘
(depending on whether ‘endpoint‘ is True or False).
step : float (only if ‘retstep‘ is True)
2.6. Ayudas 17
PIMCD2013-python, Publicación 1.0
Figura 2.9: Figura 10. Carga de los directorios en el Pythonpath, para eclipse.
2.6. Ayudas 19
PIMCD2013-python, Publicación 1.0
See Also
--------
arange : Similiar to ‘linspace‘, but uses a step size (instead of the
number of samples).
logspace : Samples uniformly distributed in log space.
Examples
--------
>>> np.linspace(2.0, 3.0, num=5)
array([ 2. , 2.25, 2.5 , 2.75, 3. ])
>>> np.linspace(2.0, 3.0, num=5, endpoint=False)
array([ 2. , 2.2, 2.4, 2.6, 2.8])
>>> np.linspace(2.0, 3.0, num=5, retstep=True)
(array([ 2. , 2.25, 2.5 , 2.75, 3. ]), 0.25)
Graphical illustration:
En el caso concreto del IDE Spyder tenemos el Object inspector. Si escribimos en la caja object la palabra
que deseamos probablemente aparezca la ayuda de forma automática. Por ejemplo, si quiero ordenar una
lista y no se cómo, podemos escribir “sort” y aparecerá la forma de utilización. Además tanto en spyder
como en Eclipse, al escribir el comando nos aparece la ayuda de forma automática como podemos ver en la
Figura 11
Por tradición se suele decir que un lenguaje de programación es más o menos complicado cuanto más nos cuesta
implementar el Hola Mundo, es decir, imprimir en pantalla estas dos palabras. Python teniendo en cuenta este
criterio es de los lenguajes más fáciles ya que con una sola línea de código podemos obtener el resultado deseado
>>> print("Hola Mundo")
Hola Mundo
int main() {
return 0;
2.6. Ayudas 21
PIMCD2013-python, Publicación 1.0
vemos que el código es notablemente más largo. Por curiosidad y a modo de avance para el tema siguiente podemos
ver como se representa una gráfica simple como la del seno en Python,
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Representamos
plt.plot(x,y)
# Mostramos en pantalla
plt.show()
1.0
0.5
0.0
0.5
1.00 2 4 6 8 10
En la sección anterior hemos podido comprobar lo sencillo y legible que es un programa Python. No obstante,
con el fin de que el código sea lo más comprensible posible para cualquiera que se disponga a utilizarlo o modi-
ficarlo se suelen añadir por convenio una serie de apartados y líneas de códico complementarias que facilitan su
comprensión. Así, como normal general todos nuestros programas de Python deben tener los siguientes apartados
2.7 Autoejecución
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
La primera línea de código sirve para decir al sistema operativo que este archivo se puede ejecutar, es decir,
si se hace click sobre el archivo, se llama al programa python y se ejecuta. Es el equivalente a los archivos
ejecutables .exe en Windows, aunque aquí el programa no está compilado, sino que se interpreta (esto último
es más lento).
La segunda línea de código sirve para poder utilizar la codificacion utf-8. De esta forma podemos poner
acentos, ñ, etc.
La tercera línea de código carga un módulo adicional que permite que la división entre números enteros
pueda dar números fraccionarios. Al ser Python un lenguaje de propósito general la división entre enteros da
lugar a enteros, lo cual suele ser problematico en cálculos científicos. NOTA: También se puede solucionar
escribiendo, en lugar de 3/4, 3./4, pero de esta forma el riesgo de cometer errores por esta causa es menor.
2.8 Cabecera
# Nombre: ejemplo_estructura_programa.py
# Propósito: Ejemplo de archivo con la estructura de un programa
#
# Origen: Propio
# Autor: José María Herrera-Fernandez y Luis Miguel Sanchez Brea
#
# Creación: 12 de Septiembre de 2013
# Historia:
#
# Dependencias: scipy, matplotlib
# Licencia: GPL
Esta sección es estrictamente voluntaria, sin embargo es muy útil cuando varios usuarios programan en el mismo
archivo.
Nombre –> El nombre del archivo que vamos a crear cuando guardemos por primera vez. En nuestro caso
ejemplo_estructura_programa.py.
Propósito –> La finalizad que se persigue al crear el programa, * Crear gráfica del seno*, * Calcular los
coeficientes de un polinomio*, etc.
Origen –> Si el origen del programa no es propio conviene saber su procedencia por si se debe consultar de
nuevo la fuente original en un futuro.
Autor(es) –> Los nombres de los creadores del programa.
Creación –> Fecha de creación del programa.
Historia –> En caso de modificaciónes posteriores es en esta seción donde se indica la modificación, la fecha
de la realización y el autor de la misma.
Dependencias –> Módulos necesarios para la ejecución del programa. Por ejemplo scipy, matplotlib..
Licencia –> Apartado correspondiente a los derechos de autor.
Como se observa, para incluir un comentario se utiliza el caracter #
2.7. Autoejecución 23
PIMCD2013-python, Publicación 1.0
En el apartado de Ayudas (2.2.4) aprendimos a utilizar el comando help para obtener información sobre el uso de
un determinado comando. El resultado es una información muy valiosa que nosotros debemos de añadir también
a nuestras funciones, programas etc. Es lo que se denomina Docstring. Las ayudas se deben de incluir inmediata-
mente despúes de la cabecera en el caso de ser una descripción general e inmediantamente después de cualquier
función, clase, etc. La metodología es simple, todo aquella información que creamos necesaria para el Docstring
debe de ir encerrada entre seis pares de comillas dobles (tres al inicio y tres al final). Además para funciones
deberemos de incluir los siguientes apartados:
(Entradas) –> (salidas). Se enumeran el tipo de entrados que necesita como entradas la función y el tipo de
salidas que nos devolverá, int, bool, float, etc
Descripción. Ecplicación de la función.
Explicación de cada entrada.
Explicación de cada salida.
Test. Ejemplo sencillo con el resultado que pueda servir como comprobador del buen uso de la función
creada.
Ejemplo de ayuda para un archivo general,
"""
Descripción: Archivo plantilla para generar programas en Python.
"""
Aunque todavía no hemos visto las funciones podemos ver un ejemplo de ayuda en una función que nos devuelve
el seno del número que entremos,
def f1(x = 3.3):
"""
* (float) --> (float)
* Descripción: Función que devuelve el seno de un entero dado.
* Entradas:
- x = entero.
* Salidas:
- y = seno(entero)
* Test:
>>> print("f1(0.7)")
0.64421768723769102
"""
y = sp.sin(x)
return y
En Python no es necesario declarar las variables, lo cual supone una gran simplificación respecto a programas
como C. Sin embargo para llamar a funciones y clases que no están en el archivo que se está ejecutando, es
necesario decirle dónde están. Se pueden declarar un módulo, o llamar a sus funciones de diversas formas. En los
siguientes ejemplos cargamos algunas o todas las funciones del módulo científico scipy.
import scipy
import scipy as sp
from scipy import *
from scipy import sin
from scipy.fftpack import fft
import matplotlib.pyplot as plt
Veamos como llamaríamos a la funcion sin dependiendo la forma en la que hayamos cargado scipy
import scipy –> “y = scipy.sin(x)”, Importamos scipy y llamamos a las funciones que tiene.
import scipy as sp –> “y = sp.sin(x)”, Renombramos scipy con un alias más corto.
from scipy import * –> “y = sin(x)”, importamos todas las funciones que contiene scipy
from scipy import sin, cos –> “y = sin(x)”, importamos únicamente sin y cos
from scipy.fftpack import fft –> Si los módulos son muy largos, se pueden hacer en varios archivos. Entonces
las funciones o clases están anidadas.
Esto es de gran utilidad por varios motivos:
Permite no estar pendiente del nombre de las funciones, clases, etc. Si el nombre se repite en dos módulos,
no hay problema, pues sabemos a qué módulo pertenece
Permite escribir código de forma modular. Para utilizar en módulo desarrollado por otro programado, por
ejemplo optica.py, lo único que hay que hacer es llamarlo antes, “import optica”
Seguidamente a las declaraciones de los módulos se pueden escribir las funciones y clases que vayamos a utilizar.
Esto no significa que el programa vaya a realizar ninguna acción. En el siguiente tema se verá cómo hay que
desarrollar el código para crear estas funciones y clases.
Una ventaja de Python es que en un mismo programa (o módulo, según definamos) podemos escribir muchas
funciones y clases a la vez, por lo que se simplifica la revisión de código y su mantenimiento. Asimismo, incluir
varias funciones en un mismo archivo permite poder pasar nuestro código a otros programadores de una forma
sencilla.
def f1(x = 3.3):
"""
* (float) --> (float)
* Descripción: Función que devuelve el seno de un entero dado.
* Entradas:
- x = entero.
* Salidas:
- y = seno(entero)
* Test:
>>> print("f1(0.7)")
0.64421768723769102
"""
y = sp.sin(x)
return y
def f2(x=3):
"""
* (float) --> (float)
* Descripción: Función que devuelve el seno de un entero dado.
* Entradas:
- x = entero.
* Salidas:
- y = cos(entero)
* Test:
>> print("f2(0.7)")
0.7648421872844885
"""
y = sp.cos(x)
return y
Python no utiliza paréntesis o llaves para decir qué código pertenece a una función. Es de esencial importancia
en Python seguir un sistema de indentación. Es decir, el código que está dentro de una función tiene que estár
indentado dentro de la función como en el ejemplo anterior para que pertenezca a la función. Para salirnos de la
función, simplemente “des-indentamos” el código.
A una función de Python se la puede llamar de dos formas: a través de otra función python o directamente. El
comportamiento de la función puede ser distinto por lo que se debe de tener cuidado. Si, por ejemplo, tenemos el
siguiente codigo al final del archivo:
if __name__ == ’__main__’:
# Creamos el array x
x = sp.linspace(0,10,100)
Llegados a este punto podemos ver nuestra programa completo que no servirá de plantilla para el resto de progra-
mas que creemos,
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_estructura_programa.py
# Propósito: Ejemplo de archivo con la estructura de un programa
#
# Origen: Propio
# Autor: José María Herrera-Fernandez y Luis Miguel Sanchez Brea
#
# Creación: 12 de Septiembre de 2013
# Historia:
#
# Dependencias: scipy, matplotlib
# Licencia: GPL
#----------------------------------------------------------------------
"""
Descripción: Archivo plantilla para generar programas en Python.
"""
import scipy
import scipy as sp
from scipy import *
from scipy import sin
from scipy.fftpack import fft
import matplotlib.pyplot as plt
def f2(x=3):
"""
* (float) --> (float)
* Descripción: Función que devuelve el seno de un entero dado.
* Entradas:
- x = entero.
* Salidas:
- y = cos(entero)
* Test:
>> print("f2(0.7)")
0.7648421872844885
"""
y = sp.cos(x)
return y
if __name__ == ’__main__’:
# Creamos el array x
x = sp.linspace(0,10,100)
Ejecutando,
1.0
0.5
0.0
0.5
1.00 2 4 6 8 10
En el apartado de generación de ayudas vimos que era conveniente añadir un apartado de verificación de la la fun-
ción que llamamos Test. Sin embargo, en ocasiones las funciones son demasiado grades para añadir este apartado.
En estas ocasiones la solución es simple, nos creamos otra función que invoque a la primera proporcionando datos
conocidos. De esta forma continuaremos probando el buen funcionamiento de nuestra función sin el inconveniente
del espacio.
Lenguajes de Programación
Existen numerosos manuales de programación, además de magnificos y extensos manuales oficiales. Sirva de
ejemplo, http://pyspanishdoc.sourceforge.net/tut/tut.html, una amplia guía en español que a pesar de ser escrita en
2005 nos proporcionará toda la informacíón que necesitamos. Nosotros en esta sección nos limitaremos a aportar
un resumen tomado de la wikipedia para que el lector pueda seguir el resto del libro de forma cómoda aunque
recomendamos visitar tanto la fuente original de la información, http://es.wikipedia.org/wiki/Python, como la guía
que acabamos de mencionar.
Python fue diseñado para ser leído con facilidad. Una de sus características es el uso de palabras donde otros
lenguajes utilizarían símbolos. Por ejemplo, los operadores lógicos !, || y && en Python se escriben not, or y and,
29
PIMCD2013-python, Publicación 1.0
respectivamente.
El contenido de los bloques de código (bucles, funciones, clases, etc.) es delimitado mediante espacios o tabula-
dores, conocidos como indentación, antes de cada línea de órdenes pertenecientes al bloque. Python se diferencia
así de otros lenguajes de programación que mantienen como costumbre declarar los bloques mediante un conjunto
de caracteres, normalmente entre llaves {}. Se pueden utilizar tanto espacios como tabuladores para identar el
código, pero se recomienda no mezclarlos.
Debido al significado sintáctico de la indentación, una instrucción debe estar contenida en línea. No obstante, si
por legibilidad se quiere dividir la instrucción en varias líneas, añadiendo una barra invertida al final de una línea,
se indica que la instrucción continúa en la siguiente.
lista = [’valor 1’,’valor 2’,’valor 3’]
cadena = ’Esto es una cadena bastante larga’
que es equivalente a
lista = [’valor 1’,’valor 2’ \
,’valor 3’]
cadena = ’Esto es una cadena’ \
’bastante larga’
3.1.2 Comentarios
Los comentarios se pueden poner de dos formas. La primera y más apropiada para comentarios largos es utilizando
la notación ‘” comentario ‘’‘, tres apostrofes de apertura y tres de cierre.
’’’
Comentario más largo en una línea en Python
’’’
El intérprete no tiene en cuenta los comentarios, lo cual es útil si deseamos poner información adicional en nuestro
código como, por ejemplo, una explicación sobre el comportamiento de una sección del programa.
3.1.3 Variables
Las variables se definen de forma dinámica, lo que significa que no se tiene que especificar cuál es su tipo de
antemano y puede tomar distintos valores en otro momento, incluso de un tipo diferente al que tenía previamente.
Se usa el símbolo = para asignar valores.
x = 1
x = "Esta variable es una cadena" # Esto es posible porque los tipos son asignados dinámicamente.
Para declarar una lista se usan los corchetes [ ], en cambio, para declarar una tupla se usan los paréntesis (
). En ambas los elementos se separan por comas, y en el caso de las tuplas es necesario que tengan como
mínimo una coma.
Tanto las listas como las tuplas pueden contener elementos de diferentes tipos. No obstante las listas suelen
usarse para elementos del mismo tipo en cantidad variable mientras que las tuplas se reservan para elementos
distintos en cantidad fija.
Para acceder a los elementos de una lista o tupla se utiliza un índice entero (empezando por “0”, no por “1”).
Se pueden utilizar índices negativos para acceder elementos a partir del final, por ejemplo para acceder al
último índice deberíamos de usar “-1”.
Las listas se caracterizan por ser mutables, es decir, se puede cambiar su contenido en tiempo de ejecución,
mientras que las tuplas son inmutables ya que no es posible modificar el contenido una vez creada.
Listas
>>> lista = ["abc", 42, 3.1415] # Definición de una lista con elementos de distinto tipo.
>>> lista[0] # Acceder a un elemento por su índice, en este caso al primero.
’abc’
>>> lista[-1] # Acceder a un elemento usando un índice negativo, en este caso el último.
3.1415
>>> lista.append(True) # Añadir un elemento al final de la lista.
>>> lista
[’abc’, 42, 3.1415, True]
>>> del lista[3] # Borra un elemento de la lista usando un índice (en este caso: True).
>>> lista
[’abc’, 42, 3.1415]
>>> lista[0] = "xyz" # Re-asignar el valor del primer elemento de la lista.
>>> lista
[’xyz’, 42, 3.1415]
>>> lista[0:2] # Mostrar los elementos de la lista del índice "0" al "2" (sin incluir este último)
[’xyz’, 42]
>>> lista_anidada = [lista, [True, 42L]] # Es posible anidar listas
>>> lista_anidada
Tuplas
>>> tupla = ("abc", 42, 3.1415) # Definición de una tupla con elementos de distinto tipo.
>>> tupla[0] # Acceder a un elemento por su índice en este caso al primero.
’abc’
>>> del tupla[0] # No es posible borrar (ni añadir) un elemento en una tupla, lo que provocará una
( Excepción )
# Tampoco es posible re-asignar el valor de un elemento en una tupla, lo que también provocará una
>>> tupla[0] = "xyz"
( Excepción )
>>> tupla[0:2] # Mostrar los elementos de la tupla del índice "0" al "2" (sin incluir este último)
(’abc’, 42)
>>> tupla_anidada = (tupla, (True, 3.1415)) # También es posible anidar tuplas.
>>> 1, 2, 3, "abc" # Esto también es una tupla, aunque es recomendable ponerla entre paréntesis (
(1, 2, 3, ’abc’)
>>> (1) # Aunque entre paréntesis, esto no es una tupla, ya que no posee al menos una coma, por lo
1
>>> (1,) # En cambio, en este otro caso, sí es una tupla.
(1,)
>>> (1, 2) # Con más de un elemento no es necesaria la coma final.
(1, 2)
>>> (1, 2,) # Aunque agregarla no modifica el resultado.
(1, 2)
3.1.6 Diccionarios
Para declarar un diccionario se usan las llaves { }. Contienen elementos separados por comas, donde cada
elemento está formado por un par clave : valor (el símbolo : separa la clave de su valor correspondiente).
Los diccionarios son mutables, es decir, se puede cambiar el contenido de un valor en tiempo de ejecución.
En cambio, las claves de un diccionario deben ser inmutables. Esto quiere decir, por ejemplo, que no podre-
mos usar ni listas ni diccionarios como claves.
El valor asociado a una clave puede ser de cualquier tipo de dato, incluso un diccionario.
>>> diccionario = {"cadena": "abc", "numero": 42, "lista": [True, 42L]} # Diccionario que tiene di
>>> diccionario["cadena"] # Usando una clave, se accede a su valor.
’abc’
>>> diccionario["lista"][0] # Acceder a un elemento de una lista dentro de un valor (del valor de
True
>>> diccionario["cadena"] = "xyz" # Re-asignar el valor de una clave.
>>> diccionario["cadena"]
’xyz’
>>> diccionario["decimal"] = 3.1415927 # Insertar un nuevo elemento clave:valor.
>>> diccionario["decimal"]
3.1415927
>>> diccionario_mixto = {"tupla": (True, 3.1415), "diccionario": diccionario} # También es posible
>>> diccionario_mixto["diccionario"]["lista"][1] # Acceder a un elemento dentro de una lista, que
42L
>>> diccionario = {("abc",): 42} # Sí es posible que una clave sea una tupla, pues es inmutable.
>>> diccionario = {["abc"]: 42} # No es posible que una clave sea una lista, pues es mutable, lo
( Excepción )
3.1.7 Conjuntos
Los conjuntos se construyen mediante set(items) donde items es cualquier objeto iterable, como listas o
tuplas. Los conjuntos no mantienen el orden ni contienen elementos duplicados.
Se suelen utilizar para eliminar duplicados de una secuencia, o para operaciones matemáticas como inter-
sección, unión, diferencia y diferencia simétrica.
>>> conjunto_inmutable = frozenset(["a", "b", "a"]) # Se utiliza una lista como objeto iterable.
>>> conjunto_inmutable
frozenset([’a’, ’b’])
>>> conjunto1 = set(["a", "b", "a"]) # Primer conjunto mutable.
>>> conjunto1
set([’a’, ’b’])
>>> conjunto2 = set(["a", "b", "c", "d"]) # Segundo conjunto mutable.
>>> conjunto2
set([’a’, ’c’, ’b’, ’d’]) # Recuerda, no mantienen el orden, como los diccionarios.
>>> conjunto1 & conjunto2 # Intersección
set([’a’, ’b’])
>>> conjunto1 | conjunto2 # Unión.
set([’a’, ’c’, ’b’, ’d’])
>>> conjunto1 - conjunto2 # Diferencia (1).
set([])
>>> conjunto2 - conjunto1 # Diferencia (2).
set([’c’, ’d’])
>>> conjunto1 ^ conjunto2 # Diferencia simétrica.
set([’c’, ’d’])
Una lista por comprensión (en inglés: list comprehension) es una expresión compacta para definir listas. Al igual
que lambda, aparece en lenguajes funcionales. Ejemplos:
>>> range(5) # La función "range" devuelve una lista, empezando en 0 y terminando con el número in
[0, 1, 2, 3, 4]
>>> [i*i for i in range(5)] # Por cada elemento del rango, lo multiplica por sí mismo y lo agrega
[0, 1, 4, 9, 16]
>>> lista = [(i, i + 2) for i in range(5)].
>>> lista
[(0, 2), (1, 3), (2, 4), (3, 5), (4, 6)]
3.1.9 Funciones
Las funciones se definen con la palabra clave def, seguida del nombre de la función y sus argumentos entre
paréntesis para finalizar con :, es decir,
>>> def mi_funcion(arg1, arg2,..., argN):
Otra forma de escribir funciones, aunque menos utilizada, es con la palabra clave lambda. lambda es un
cosntructor que sirver para ejecutar funciones anónimas, es decir funciones sin ningún nombre pero que
crean una referencia a un objeto función. La sintaxis de lambda en python es
lambda <aParameterList> : <a Python expression using the parameters>
El valor devuelto en las funciones con def será el dado con la instrucción return. lambda no necesita de re-
turn para retornar un valor, ya que está implícito, puesto que la función entera debe ser una única expresión.
def
lambda
>>> suma = lambda x, y = 2: x + y
>>> suma(4) # La variable "y" no se modifica, siendo su valor: 2
6
>>> suma(4, 10) # La variable "y" sí se modifica, siendo su nuevo valor: 10
14
3.1.10 Clases
Las clases se definen con la palabra clave class, seguida del nombre de la clase y, si hereda de otra clase, el
nombre de esta.
En Python 2.x es recomendable que una clase herede de “object”, en Python 3.x esto ya no hará falta.
En una clase un “método” equivale a una “función”, y una “propiedad” equivale a una “variable”.
“__init__” es un método especial que se ejecuta al instanciar la clase, se usa generalmente para inicializar
propiedades y ejecutar métodos necesarios. Al igual que todos los métodos en Python, debe tener al menos
un parámetro, generalmente se utiliza self. El resto de parámetros serán los que se indiquen al instanciar la
clase.
Las propiedades que se desee que sean accesibles desde fuera de la clase se deben declarar usando self.
delante del nombre.
En python no existe el concepto de encapsulación, por lo que el programador debe ser responsable de asignar
los valores a las propiedades
>>> class Persona(object):
... def __init__(self, nombre, edad):
... self.nombre = nombre # Una Propiedad cualquiera.
... self.edad = edad # Otra propiedad cualquiera.
... def mostrar_edad(self): # Es necesario que, al menos, tenga un parámetro, generalmente: "s
... print self.edad # mostrando una propiedad.
... def modificar_edad(self, edad): # Modificando Edad.
... if edad < 0 or edad > 150: # Se comprueba que la edad no sea menor de 0 (algo imposibl
... return False
... else: # Si está en el rango 0-150, entonces se modifica la variable.
... self.edad = edad # Se modifica la edad.
...
>>> p = Persona("Alicia", 20) # Instanciamos la clase, como se puede ver, no se especifica el valo
>>> p.nombre # La variable "nombre" del objeto sí es accesible desde fuera.
’Alicia’
>>> p.nombre = "Andrea" # Y por tanto, se puede cambiar su contenido.
>>> p.nombre
’Andrea’
>>> p.mostrar_edad() # Podemos llamar a un método de la clase.
20
>>> p.modificar_edad(21) # Y podemos cambiar la edad usando el método específico que hemos hecho p
>>> p.mostrar_edad()
21
3.1.11 Condicionales
Una sentencia condicional if ejecuta su bloque de código interno sólo si se cumple cierta condición. Se define
usando la palabra clave if seguida de la condición, y el bloque de código. Condiciones adicionales, si las hay, se
introducen usando elif seguida de la condición y su bloque de código. Todas las condiciones se evalúan secuen-
cialmente hasta encontrar la primera que sea verdadera, y su bloque de código asociado es el único que se ejecuta.
Opcionalmente, puede haber un bloque final (la palabra clave else seguida de un bloque de código) que se ejecuta
sólo cuando todas las condiciones resultan falsas.
>>> verdadero = True
>>> if verdadero: # No es necesario poner "verdadero == True".
... print "Verdadero"
... else:
... print "Falso"
...
Verdadero
>>> lenguaje = "Python"
>>> if lenguaje == "C": # lenguaje no es "C", por lo que este bloque se obviará y evaluará la sigu
... print "Lenguaje de programación: C"
... elif lenguaje == "Python": # Se pueden añadir tantos bloques "elif" como se quiera.
... print "Lenguaje de programación: Python"
... else: # En caso de que ninguna de las anteriores condiciones fuera cierta, se ejecutaría este
... print "Lenguaje de programación: indefinido"
...
Lenguaje de programación: Python
>>> if verdadero and lenguaje == "Python": # Uso de "and" para comprobar que ambas condiciones son
... print "Verdadero y Lenguaje de programación: Python"
...
Verdadero y Lenguaje de programación: Python
El bucle for es similar a foreach en otros lenguajes. Recorre un objeto iterable, como una lista, una tupla o un
generador, y por cada elemento del iterable ejecuta el bloque de código interno. Se define con la palabra clave for
seguida de un nombre de variable, seguido de in, seguido del iterable, y finalmente el bloque de código interno.
En cada iteración, el elemento siguiente del iterable se asigna al nombre de variable especificado:
>>> lista = ["a", "b", "c"]
>>> for i in lista: # Iteramos sobre una lista, que es iterable.
... print i
...
a
b
c
>>> cadena = "abcdef"
>>> for i in cadena: # Iteramos sobre una cadena, que también es iterable.
... print i, # Añadiendo una coma al final hacemos que no introduzca un salto de línea, sino u
...
a b c d e f
El bucle while evalúa una condición y, si es verdadera, ejecuta el bloque de código interno. Continúa evaluando y
ejecutando mientras la condición sea verdadera. Se define con la palabra clave while seguida de la condición, y a
continuación el bloque de código interno:
>>> numero = 0
>>> while numero < 10:
... numero += 1
... print numero,
...
1 2 3 4 5 6 7 8 9
3.1.14 Módulos
Existen muchas propiedades que se pueden agregar al lenguaje importando módulos, que son “minicódigos” (la
mayoría escritos también en Python) que proveen de ciertas funciones y clases para realizar determinadas tareas.
Un ejemplo es el módulo Tkinter, que permite crear interfaces gráficas basadas en la biblioteca Tk. Otro ejemplo es
el módulo os, que provee acceso a muchas funciones del sistema operativo. Los módulos se agregan a los códigos
escribiendo import seguida del nombre del módulo que queramos usar.
>>> import os # Módulo que provee funciones del sistema operativo
>>> os.name # Devuelve el nombre del sistema operativo
’posix’
>>> os.mkdir("/tmp/ejemplo") # Crea un directorio en la ruta especificada
>>> import time # Módulo para trabajar con fechas y horas
>>> time.strftime("%Y-%m-%d %H:%M:%S") # Dándole un cierto formato, devuelve la fecha y/o hora ac
’2010-08-10 18:01:17’
En Python todo es un objeto (incluso las clases). Las clases, al ser objetos, son instancias de una metaclase. Python
además soporta herencia múltiple y polimorfismo.
>>> cadena = "abc" # Una cadena es un objeto de "str"
>>> cadena.upper() # Al ser un objeto, posee sus propios métodos
’ABC’
>>> lista = [True, 3.1415] # Una lista es un objeto de "list"
>>> lista.append(42L) # Una lista también (al igual que todo) es un objeto, y también posee sus pr
>>> lista
[True, 3.1415, 42L]
Módulos Científicos
Con los módulos generales, Phyton es dificilmente aplicable al entorno científico. Afortunadamente la comunidad
de desarrolladores pronto puso solución a este inconveniente mediante la creación de potentes módulos de cálculo
numérico. Hoy día existen módulos específicos de casi todas las áreas cientifico-ingenieriles. En este tema presen-
taremos brevemente algunos de los módulos más utilizados y explicaremos diversos ejemplos que nos ayudaran a
aprender su utilización de forma rápida e intuitiva.
.
4.1 Numpy
Uno de los módulos más importantes de Python es Numpy. El origen de Numpy se debe principalmente al dise-
ñador de software Jim Hugunin quien diseñó el módulo Numeric para dotar a Python de capacidades de cálculo
similares a las de otros softwares como MATLAB. Posteriormente, mejoró Numeric incorporando nuevas funcio-
nalidades naciendo lo que hoy conocemos como Numpy.
Numpy es el encargado de añadir toda la capacidad matemática y vectorial a Python haciendo posible operar
con cualquier dato numérico o array (posteriormente veremos qué es un array). Incorpora operaciones tan básicas
como la suma o la multiplicación u otras mucho más complejas como la transformada de Fourier o el álgebra lineal.
Además incorpora herramientas que nos permiten incorporar código fuente de otros lenguajes de programación
como C/C++ o Fortran lo que incrementa notablemente su compatibilidad e implementación.
Para poder utilizar este módulo sino hemos instalado python(x,y) lo primero que debemos de hacer es descargarnos
la última versión desde el sitio oficial http://www.scipy.org/Download. En caso de que nuestro sistema operativo
sea Linux probablemente venga instalado dentro de Python. De no ser así debemos instalarlo por el método
habitual. Para más información y otros sistemas operativos consultar las notas oficiales de instalación. Para cargar
el módulo en Python y poder llamarlo de formar más cómoda es de uso extendido la utilización del alias np,
import numpy as np
En el ámbito científico es frecuente almacenar y trasladar paquetes de datos en archivos escritos en, por ejemplo,
código ASCII. Python incorpora varias funciones que nos permiten la lectura y escritura en estos archivos facili-
37
PIMCD2013-python, Publicación 1.0
tando su manejo. Un ejemplo de ello es el comando “loadtxt” que nos permite cargar los datos contenidos en un
archivo de texto o dat.
Ejemplo 4.1
>>> import numpy as np # Importamos numpy como el alias np
# Cargamos los datos de archivo.txt en datos. En este caso el delimitador es la coma.
>>> datos = numpy.loadtxt(’C:\ruta\a\tu\archivo.txt’, delimiter = ’,’)
Ejemplo 4.2
>>> import numpy as np # Importamos numpy como el alias np.
>>> archivoDatos = np.loadtxt(’codigo\archivoDatos.dat’)
>>> print (archivoDatos) # Presentamos en pantalla los datos cargados.
Ejemplo 4.3
>>> from StringIO import StringIO # StringIO se comporta como un archivo objeto.
>>> c = StringIO("0 1\n2 3")
>>> np.loadtxt(c)
array([[ 0., 1.],
[ 2., 3.]])
4.1.2 Arrays
Con toda probabilidad, el lector que haya realizado un acercamiento a cualquier lenguaje de programación habrá
oído hablar de arrays. Un array es el termino que traslada el concepto matemático de vector o matriz a la progra-
mación añadiéndole la noción de almacenamiento en memoria. Los arrays disponen en su interior de una serie de
elementos dispuestos en filas y/o columnas dependiendo de la dimensión.
El desarrollo y la principal finalidad del módulo Numpy es la creación y modificación de arrays multidimensio-
nales. Para este fin utilizaremos las clase ndarray del ingles N-dimensional array o usando su alias simplemente
array (no confundir con la clase array.array que ofrece menos funcionalidad). En Python cada clase puede tener
atributos que se pueden llamar con el método visto anteriormente o simplemente escribiendo a continuación de
la clase un punto y el atributo. En la mayoría de los IDEs al cargar la clase y escribir el punto aparecen todos los
atributos disponibles en orden alfabético por lo que en caso de dudar siempre podemos utilizar este método para
escribir el comando. En el caso de ndarray los principales atributos son los siguientes:
ndarray.ndim –> Proporciona el número de dimensiones de nuestro array. El array identidad es un array
cuadrado con una diagonal principal unitaria.
ndarray.shape –> Devuelve la dimensión del array, es decir, una tupla de enteros indicando el tamaño del
array en cada dimensión. Para una matriz de n filas y m columnas obtendremos (n,m).
ndarray.size –> Es el número total de elementos del array.
ndarray.dtype –> Es un objeto que describe el tipo de elementos del array.
ndarray.itemsize –> devuelve el tamaño del array en bytes.
ndarray.data –> El buffer contiene los elementos actuales del array.
Veamos un ejemplo de su uso con un array sencillo de diez elementos,
Ejemplo 4.4
>>> import numpy as np # Importamos numpy como el alias np
>>> miArray = np.arange(10) # Creamos un array de 0 a 9 separados de uno en uno
>>> miArray # Presentamos en pantalla el array creado
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> type(miArray) #Comprobamos que es un ndarray
<type ’numpy.ndarray’>
>>> miArray.ndim # Consultamos el número de dimensiones
1
>>> miArray.shape # Consultamos la dimensión
(10,)
>>> miArray.size # Consultamos la dimensión
10
>>> miArray.dtype # Consultamos el tipo de elementos del array
dtype(’int64’)
>>> miArray.itemsize # tamaño en bytes
8
>>> miArray.data # Consultamos el buffer de memoria.
<read-write buffer for 0x2fb57a0, size 80, offset 0 at 0x2f664b0>
Como hemos mencionando anteriormente, los arrays son unos de los elementos más utilizados por los progra-
madores bajo cualquier lenguaje. Este gran uso hace que dispongamos de una gran variedad de comandos para
la creación de arrays: arrays unidimensionales, multidimensionales, nulos, unitarios, secuenciales, de números
aleatorios, etc. Dependiendo de las necesidades de nuestro problema escogeremos la opción más adecuada.
identity(n,dtype). Devuelve la matriz identidad, es decir, uma matriz cuadrada nula excepto en su diagonal
principal que es unitaria. n es el número de filas (y columnas) que tendrá la matriz y dtype es el tipo de dato.
Este argumento es opcional. Si no se establece, se toma por defecto como flotante.
ones(shape,dtype). Crea un array de unos compuesto de shape elementos.
zeros(shape, dtype). Crea un array de ceros compuesto de “shape” elementos”.
empty(shape, dtype). Crea un array de ceros compuesto de “shape” elementos” sin entradas.
eye(N, M, k, dtype). Crea un array bidimensional con unos en la diagonal k y ceros en el resto. Es similar
a identity. Todos los argumentos son opcionales. N es el número de filas, M el de columnas y k es el índice
de la diagonal. Cuando k=0 nos referimos a la diagonal principal y por tanto eye es similar a identity.
arange([start,]stop[,step,],dtype=None). Crea un array con valores distanciados step entre el valor inicial
star y el valor final stop. Si no se establece step python establecerá uno por defecto.
linspace(start,stop,num,endpoint=True,retstep=False). Crea un array con valor inicial start, valor final
stop y num elementos.
meshgrid(x,y). Genera una matriz de coordenadas a partir de dos los arrays x, y.
En el siguiente ejemplo podemos ver su utilización,
Ejemplo 4.5
>>> from numpy import * # Importamos todo el módulo numpy.
>>> zeros( (3,4) ) # Creamos un array nulo de 3 filas y 4 columnas.
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
>>> ones(9) # Creamos un array unitario de 1 fila y 9 columnas,
array([ 1., 1., 1., 1., 1., 1., 1., 1., 1.])
>>> empty( (2,3) ) # Creamos un array sin entradas de 2x3.
array([[ 3.73603959e-262, 6.02658058e-154, 6.55490914e-260],
[ 5.30498948e-313, 3.14673309e-307, 1.00000000e+000]])
>>> arange( 10, 30, 5 ) # Creamos un array con inicio en 10 final en 30 y paso de 5.
array([10, 15, 20, 25])
>>> linspace( 0, 2, 9 ) # Array de 9 elementos de 0 a 2
array([ 0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. ])
>>> x = linspace( 0, 2*pi, 100 ) # Para gráficas conviene tener muchos puntos, en este caso 100
>>> X, Y = np.meshgrid([1,2,3], [4,5,6,7]) # Generamos una matriz de coordenadas.
>>> X
array([[1, 2, 3],
[1, 2, 3],
[1, 2, 3],
[1, 2, 3]])
>>> Y
array([[4, 4, 4],
[5, 5, 5],
4.1. Numpy 39
PIMCD2013-python, Publicación 1.0
[6, 6, 6],
[7, 7, 7]])
4.1.3 Matrices
Un subtipo especial de array bidimensional de NumPy es la matriz. Una matriz es como una array, excepto que la
multiplicación de matrices (y exponenciación) reemplaza la multiplicación elemento a elemento. Las matrices se
crean mediante la función matrix.
Ejemplo 4.6
>>> from numpy import matrix # Importamos matrix del módulo numpy.
>>> a = matrix([[1,3,-5],[3,4,2],[-5,2,0]])
matrix([[ 1, 3, -5],
[ 3, 4, 2],
[-5, 2, 0]])
>>> b = matrix([[1],[5],[3]])
>>> b
matrix([[1],
[5],
[3]])
>>> a*b
matrix([[ 1],
[29],
[ 5]])
Definidas las matrices, podemos hacer uso de todas las funciones relacionadas con el álgebra lineal como la
transposición, la diagonalización etc.
Ejemplo 4.7
>>> b.T # Calculamos la transpuesta de *b*.
matrix([[1, 5, 3]])
>>> b.H # Calculamos la hermítica (transpuesta y conjugada) de *b*.
matrix([[1, 5, 3]])
>>> c = a.I # Calculamos la inversa de *b*.
>>> c
matrix([[ 0.02439024, 0.06097561, -0.15853659],
[ 0.06097561, 0.15243902, 0.10365854],
[-0.15853659, 0.10365854, 0.0304878 ]])
>>> a*c # Multiplicamos las matrices *a* y *c*.
matrix([[ 1.00000000e+00, -5.55111512e-17, -6.93889390e-18],
[ 0.00000000e+00, 1.00000000e+00, 4.16333634e-17],
[ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
Con el paquete linalg podemos calcular el determinante (det), resolver ecuaciones lineales (solve) y calcular auto-
valores y autovectores.
Ejemplo 4.8
>>> linalg.det(a) # Cálculo del determinante.
-164.0
>>> d = linalg.solve(a,b) # Resolución de ecuaciones lineales.
>>> d
matrix([[-0.14634146],
[ 1.13414634],
[ 0.45121951]])
>>> a*d
matrix([[ 1.],
[ 5.],
[ 3.]])
>>> e = linalg.eig(a) # Cálculo de autovalores y autovectores.
>>> e[0]
La función eig devuelve una tupla donde el primer elemento es un array de autovalores y el segundo es una matriz
que contiene a los autovectores.
El módulo numpy contiene todas las operaciones usuales entre arrays como las matemáticas (suma, resta, multi-
plicación, etc), las lógicas (and, or, xor, etc), binarias, ... Lo más fácil y sencillo es en caso de duda consultar la
documentación oficial. De particular interés en óptica es la transformada de Fourier,
Ejemplo 4.9
>>> import numpy as np # Importamos numpy como el alias np.
>>> x=np.linspace(0,1,16)
>>> y=np.sin(x)
>>> print np.fft.fft(y)
array([ 6.82834723+0.j , -0.72886897+1.98380961j,
-0.46469805+0.9337634j , -0.42166285+0.57663662j,
-0.40711196+0.38478538j, -0.40066563+0.25695308j,
-0.39747563+0.15924205j, -0.39594637+0.07645992j,
-0.39548834+0.j , -0.39594637-0.07645992j,
-0.39747563-0.15924205j, -0.40066563-0.25695308j,
-0.40711196-0.38478538j, -0.42166285-0.57663662j,
-0.46469805-0.9337634j , -0.72886897-1.98380961j])
obsérvese como se ha formado en número complejo 2j. En python los números complejos se forman (parte real
signo jparte imaginaria), es decir, separando parte real e imaginaria y sin ningún signo multiplicativo entre el
coeficiente y el numero imaginario j.
Ejemplo 4.10
>>> numero_complejo = (1 + 1j) # Defino el número complejo.
>>> print(numero_complejo) # Lo presento en pantalla.
(1+1j)
>>> type(numero_complejo) # Compruebo el tipo.
complex
4.2 Scipy
4.2. Scipy 41
PIMCD2013-python, Publicación 1.0
Scipy es una librería de herramientas numéricas para Python que se distribuye libremente. El módulo scipy con-
fiere al lenguaje general Python capacidades de cálculo numérico de gran capacidad. Scipy además de poseer en
su núcleo a Numpy, posee módulos para optimización de funciones, integración, funciones especiales, resolución
de ecuaciones diferenciales ordinarias y otros muchos aspectos.
Tanto Scipy como Numpy tienen versiones de su documentación en pdf y html en http://docs.scipy.org/. También
se puede obtener ayuda de las funciones y de los módulos mediante
>>> sp.info(funcion)
Parameters
----------
func : callable func(x,*args)
The objective function to be minimized.
x0 : ndarray
Initial guess.
args : tuple
Extra arguments passed to func, i.e. ‘‘f(x,*args)‘‘.
callback : callable
Called after each iteration, as callback(xk), where xk is the
current parameter vector.
Returns
-------
xopt : ndarray
Parameter that minimizes function.
fopt : float
Value of function at minimum: ‘‘fopt = func(xopt)‘‘.
iter : int
Number of iterations performed.
funcalls : int
Number of function calls made.
warnflag : int
1 : Maximum number of function evaluations made.
2 : Maximum number of iterations reached.
allvecs : list
Solution at each iteration.
Other parameters
----------------
xtol : float
Relative error in xopt acceptable for convergence.
ftol : number
Relative error in func(xopt) acceptable for convergence.
maxiter : int
Maximum number of iterations to perform.
maxfun : number
Maximum number of function evaluations to make.
full_output : bool
Set to True if fopt and warnflag outputs are desired.
disp : bool
Set to True to print convergence messages.
retall : bool
4.2. Scipy 43
PIMCD2013-python, Publicación 1.0
Notes
-----
Uses a Nelder-Mead simplex algorithm to find the minimum of function of
one or more variables.
Otra forma de buscar información es con el comando source, que lista el código de esa función,
>>> sp.source(sp.linspace)
En scipy las funciones universales (suma, resta, division, etc.) se han alterado para no producir errores de coma
flotante cuando se encuentran errores; por ejemplo se devuelve NaN e Inf en los arrays en lugar de errores. Para
ayudar a la detección de estos eventos, hay disponibles varias funciones como sp.isnan, sp.isinfinite, sp.isinf.
Además se han modificado algunas funciones (log, sqrt, funciones trigonometricas inversas) para devolver valores
complejos en lugar de NaN (por ejemplo sp.sqrt(-1) devuelve 1j).
Dada la gran cantidad de módulos contenidos en Scipy, en esta sección nos limitaremos en dar ejemplos de algunos
de ellos recordando que el camino ideal para el aprendizaje es la lectura de los manuales oficiales.
Desde la educación secundaría uno de los problemas matemáticos más comunes es el de encontrar el mínimo de
una función. Si la función es simple no necesitamos python para encontrar las posibles soluciones, sin embargo,
si la función es compleja o simplemente queremos automatizar el calculo la respuesta es fmin el cual se encuentra
en scipy.optimize. En nuestro caso vamos a tratar de encontrar algún mínimo de intensidad de la diffracción que
resulta a la salida de una rendija. Esta intensidad viene dada por la función sinc, (ecuación (4.1)).
𝑠𝑖𝑛(𝜋 · 𝑎 · 𝑠𝑖𝑛(𝑥))
𝐼 = 𝐼0 (4.1)
𝜋 · 𝑎 · 𝑠𝑖𝑛(𝑥)/𝜆
donde a es el ancho de la rendija, x es el ángulo y 𝜆 es la longitud de onda. Como nos proponemos mantener
nuestro programa, lo primero que vamos a hacer es escribir la autoejecución, la cabecera y la descripción de
nuestro programa tal y como aprendimos en el tema anterior.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_scipy_uso_fmin.py
# Propósito: Aprender el uso de fmin para minimizar funciones
#
# Origen: Propio
# Autor: José María Herrera-Fernandez
#
# Creación: 17 de Septiembre de 2013
# Historia:
#
# Dependencias: scipy, matplotlib
# Licencia: GPL
#----------------------------------------------------------------------
"""
Descripción: Ejemplo de cómo usar la función fmin perteneciente a scipy.optimize para
encontrar el mínimo de una función.
"""
Definimos el sistema físico de unidades y la función sinc(x) en python. Para ello recurrimos al comando def para
indicar que vamos a definir una función, el nombre que le vamos a dar a nuestra función que en este caso es sinc
y por último los argumentos de nuestra función. Para nuestro ejemplo con un único argumento nos valdrá, (x),
# Definimos el sistema físico de unidades
um = 1 # micras
mm = 100 * um # milímetros
la función fmin necesita de valores iniciales a modo de semilla para comenzar las iteraciones. En nuestro caso
hemos elegido como valor 0.001,
# fmin necesita una semilla así que se la proporcionamos.
semilla = 0.001
y por último presentamos en pantalla el resultado mediante un print. Como podemos observar, tomamos direc-
tamente el valor calculado para el print. Esto lo conseguimos gracias al simbolo %. La utilización es sencilla, en
el lugar donde queramos que aparezca el valor de nuestra variable escribimos el símbolo % seguido del número
de digitos que queramos que tenga la parte real y por últipo el tipo de numero que queremos mostrar. En nuestro
caso %1.2f significa un digito real, dos decimales y el tipo float. Además fuera de las comillas del print debemos
de poner otro % y entre paréntesis la variable a la que llamamos (en nuestro caso x_minimo_intensidad y sinc
(x_minimo_intensidad) ).
# Presenta en pantalla el mínimo y el valor de la función en ese punto.
print (’El valor de x en el que se encuentra el mínimo es %1.2f que se corresponde con y = %1.2f’
% (x_minimo_intensidad , sinc (x_minimo_intensidad )))
Ejecutando,
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 14
Function evaluations: 28
El valor de x en el que se encuentra el mínimo es 0.02 que se corrsponde con y = 0.00
4.2. Scipy 45
PIMCD2013-python, Publicación 1.0
La función sinc(x) tiene bastantes mínimos absolutos. En la imagen podemos ver señalados el encontrado y el
simétrico en rojo y la semilla en verde,
0.8
0.6
Intensidad
0.4
0.2
Otro camino para encontrar mínimos es mediante el uso de fminbound como vemos en este ejemplo. Sea la función
(︁ 𝑎𝜋𝑥 )︁
𝑦 = −𝑐𝑜𝑠 + 𝑐𝑥𝑑 (4.2)
𝑏
donde a, b, c, d son coeficientes y x es la variable. Para definirla en python vamos a crear una función cuyos
argumentos sean estos parámetros.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_scipy_minimizacion.py
# Proposito: Aprender como encontrar minimos locales con python.
#
# Origen: Propio a partir de la documentacion oficial.
# Autor: Jose Maria Herrera-Fernandez, Luis Miguel Sanchez-Brea
#
# Creacion: 18 de Septiembre de 2013
# Historia:
#
# Dependencias: scipy
# Licencia: GPL
#----------------------------------------------------------------------
"""
Descripcion: Encuentra minimos locales de una funcion escalar en el intervalo (x1,x2 usando el met
"""
"""
y = -sp.cos(a*sp.pi*x/b) + c*x**d
return y
4.2. Scipy 47
PIMCD2013-python, Publicación 1.0
1.0
0.5
y
0.0
0.5
Para este ejemplo imaginaremos que una heladería cuyas previsiones de venta en las cinco primeras horas viene
dada por la función,
−𝑏𝑥2
(︁ )︁
𝑣𝑒𝑛𝑡𝑎𝑠 = 𝑒 2𝑑2 + 𝑐𝑥 (4.3)
siendo a, b, c y d coeficientes desconocidos que el propietario quiere saber para mejorar las ventas. Lo primero
que vamos a hacer es crear nuestro archivo .py según la plantilla junto con la importación de los módulos que
vamos a utilizar.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_scipy_ajusteFunciones.py
# Propósito: Aprender el uso de curve_fit para ajustar funciones.
#
# Origen: Propio a partir del ejemplo de la documentación oficial.
"""
Descripción: Ejemplo de cómo usar la función curve_fit perteneciente a scipy.optimize para
ajustar una función a una curva.
"""
return a*sp.exp(-b*x**2/(2*d**2)) + c * x
Al no tener datos experimentales reales, vamos a crearnos unos a partir de nuestra función. Es lo que en cálculo
científico se denomina añadir ruido. Para ell nos definimos una nueva función que a partir de unos coeficientes
inventados crea un array y al que añadimos un array de numeros aleatorios de la misma dimension que x mediante
sp.random.normal que ponderamos con el factor k.
# Añadimos ruido a la función
x = sp.linspace(0, 5,30)
y = mi_funcion(x, 2.5, 1.3, 0.5,1)
def ruido(x,y,k):
"""
* (array, array, float) --> (array)
* Descripción: definición de una función general que mete ruido a la función creada.
De esta forma simulamos puntos experimentales.
* Entradas:
- x, y = arrays dimensionales.
* Salidas:
- yn = array dimensional con los datos experimentales simulados
* Test:
>>> x = sp.linspace(-5, 5,30)
>>> y = mi_funcion(x, 2.5, 1.3, 0.5,1)
>>> yn = ruido(x,y,k)
[ -1.6507892 -4.94424381 5.15178988 2.2186972 -5.84069527
4.33003238 -1.7888872 -0.30041015 -7.1204506 -2.283254
6.81036412 5.39317282 -8.57465789 -1.53478417 10.00896415
4.2. Scipy 49
PIMCD2013-python, Publicación 1.0
yn = y + k * sp.random.normal(size = len(x))
return yn
Ajustamos nuestros datos experimentales recien creados a la función inicial mediante curve_fit. Los resultados los
vamos a almacener en coeficientes_optimizados y covarianza_estimada que es la covarianza de los coeficientes
optimizados,
# Ajustamos los datos experimentales a nuestra función y los almacenamos
coeficientes_optimizados, covarianza_estimada = curve_fit(mi_funcion, x, yn)
obteniendo,
Coeficientes optimizados: [ 2.57706857 64.3221008 0.50208527 6.86175853]
Covarianza estimada: [[ 4.92845597e-03 5.04794116e+06 -6.29090049e-05 2.69252336e+05]
[ 5.04794117e+06 2.20479587e+16 -3.76113582e+05 1.17601699e+15]
[ -6.29090094e-05 -3.76113634e+05 6.21152142e-05 -2.00615411e+04]
[ 2.69252337e+05 1.17601699e+15 -2.00615406e+04 6.27276188e+13]]
Nota: En el archivo se añade también el código para la representación sin embargo se explicará cuando hablemos
del módulo Matplotlib.
Scipy dispone de un paquete muy interesante llamando special donde se encuentran funciones como airy o bessel,
http://docs.scipy.org/doc/scipy/reference/tutorial/special.html. Como ejemplo de este paquete nosotros vamos a
calcular la función de Bessel de orden cero. La ecuación diferencial de Bessel viene definida por,
𝑑2 𝑑𝑦 (︀ 2
𝑥2 + + 𝑥 − 𝛼2 𝑦 = 0
)︀
+𝑥 (4.4)
𝑑𝑥 2 𝑑𝑐
si 𝛼𝜖𝑁 ,
𝑑2 𝑑𝑦 (︀ 2
𝑥2 + + 𝑥 − 𝑛2 𝑦 = 0
)︀
+𝑥 (4.5)
𝑑𝑥 2 𝑑𝑐
cuya solución es,
∞ 𝑘 (︀ 𝑥 )︀𝑛+2𝑘
∑︁ (−1) 2
𝐽𝑛 (𝑥) = (4.6)
𝑘! (𝑛 + 𝑘)!
𝑘=0
para n = 0 y n = 1 obtenemos las funciones de Bessel de orden 0 y 1. Estas funciones son de gran inportancia ya
que las funciones del resto de ordenes se pueden escribir en función de estas dos.
(︀ 𝑥 )︀2 (︀ 𝑥 )︀4 (︀ 𝑥 )︀6
2 2 2 (4.7)
𝐽0 (𝑥) = 1 − 2 + 2 − 2 + ···
(1!) (2!) (3!)
en python,
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_scipy_bessel.py
# Propósito: Aprender el uso el paquete special de scipy.
#
# Origen: Propio.
# Autor: José María Herrera-Fernandez, Luis Miguel Sánchez-Brea
#
# Creación: 18 de Septiembre de 2013
# Historia:
#
# Dependencias: scipy, matplotlib
# Licencia: GPL
#----------------------------------------------------------------------
"""
Descripción: Cálculo de los coeficientes de orden cero y uno de la función de Bessel
contenida en el paquete special.
"""
from scipy import special # Importamos scipy.special
import scipy as sp # Importamos scipy como el alias sp
import matplotlib.pyplot as plt # Importamos matplotlib.pyplot como el alias plt.
4.2. Scipy 51
PIMCD2013-python, Publicación 1.0
Orden cero
1.0 Orden uno
0.5
J(x)
0.0
0.5
0 10 20 30 40 50
x
Nota: En el archivo se añade también el código para la representación sin embargo se explicará cuando hablemos
del módulo Matplotlib.
Gracias a integrate de Scipy también podemos realizar integrales en Python. La primera integral que vamos a
realizar es
𝑦 = 𝑒(−𝑥) (4.9)
en el intervalo de 0 a ∞
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_scipy_integrales.py
# Propósito: Aprender como integrar con python.
#
# Origen: Propio .
# Autor: José María Herrera-Fernandez, Luis Miguel Sánchez-Brea
#
# Creación: 18 de Septiembre de 2013
# Historia:
#
# Dependencias: scipy
# Licencia: GPL
#----------------------------------------------------------------------
"""
Descripción: Ejemplo de cómo usar la función integrate para realizar integrales
numéricas en python.
"""
# Los devuelvo
return integrate.quad(exponencial_decreciente ,limite_inferior,limite_superior)
donde podemos ver que hemos creado un if para mostrar o no los resultados en pantalla. Ejecutando,
>>> integral_1(limite_inferior = 0, limite_superior = sp.inf, mostrar_resultados = True)
La integral entre 0.00 y inf es
(1.0000000000000002, 5.842606742906004e-11)
De la misma forma podemos además tener en cuenta coeficientes de la función que pueden variar,
def integral_2(limite_inferior, limite_superior,a,b, mostrar_resultados):
"""
(float,float,float, float, bool) -->(float, float)
* Descripción: Calculo de la integral de limite_inferior a limite_superior de
la función y = ax^(b)
* Entradas:
- limite_inferior = inicio del intervalo de integración.
- limite_superior = fin del intervalo de integración.
- a,b = coeficientes de la función
* Salidas:
- y = integral
- err = error cometido.
* Test:
>>> y = integral_2(limite_inferior = 0, limite_superior = 1,a = 4, b = 5, mostrar_resultados =
La integral vale 0.67, y el error cometido 0.00
"""
4.2. Scipy 53
PIMCD2013-python, Publicación 1.0
# Calculo la integral
y, err = integrate.quad(funcion, limite_inferior, limite_superior, args=(a,b,))
# Los devuelvo
return y, err
donde también hemos añadido el if con una pequeña modificación. Python nos permite ahorrarnos “== True”.
Ejecutando,
>>> y = integral_2(limite_inferior = 0, limite_superior = 1,a = 2, b = 3, mostrar_resultados = Tru
La integral vale 0.50, y el error cometido 0.00
Otra opción interesante de python es poder interpolar a partir de nuestros datos experimentales. Como ejemplo
interpolaremos en una dimensión mediante interpolate.interp1d,
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_scipy_interpolar.py
# Propósito: Aprender como interpolar con python.
#
# Origen: Propio a partir de la documentación oficial.
# Autor: José María Herrera-Fernandez, Luis Miguel Sánchez-Brea
#
# Creación: 18 de Septiembre de 2013
# Historia:
#
# Dependencias: scipy
# Licencia: GPL
#----------------------------------------------------------------------
"""
Descripción: Ejemplo de cómo hacer una interpolación en python.
"""
import scipy as sp # Importamos scipy como el alias sp
from scipy import interpolate # Importamos interpolate de scipy
import matplotlib.pyplot as plt # Importamos matplotlib.pyplot como el alias plt
# Interpolamos
interpolacion = interpolate.interp1d(x, y)
# Evaluamos x2 en la interpolación
y2 = interpolacion(x2)
# Creamos la figura
plt.figure
# Añadimos la leyenda
plt.legend((’Datos conocidos’, ’Datos experimentales’))
# Mostramos en pantalla
plt.show()
1.0
Datos conocidos
0.9 Datos experimentales
0.8
0.7
Eje y
0.6
0.5
0.4
Scipy posibilita la opción de clacular las raices de un polinomio de una forma rápida y sencilla,
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_scipy_raicesPolinomio.py
# Propósito: Aprender como calcular las raices de un polinomio con python.
#
# Origen: Propio a partir de la documentación oficial.
# Autor: José María Herrera-Fernandez, Luis Miguel Sánchez-Brea
4.2. Scipy 55
PIMCD2013-python, Publicación 1.0
#
# Creación: 20 de Septiembre de 2013
# Historia:
#
# Dependencias: scipy, matplotlib
# Licencia: GPL
#----------------------------------------------------------------------
"""
Descripción: Ejemplo de cómo calcular las raices de un polinomio mediante scipy.
"""
# Creamos un polinomio
polinomio = [1.3,4,.6,-1] # polinomio = 1.3 x^3 + 4 x^2 + 0.6 x - 1
obteniendo,
Las raices son -2.82, -0.67, 0.41.
10
y
15
20
25 4 3 2 1 0 1
x
4.3 Matplotlib
Matplotlib es el módulo de dibujo de gráficas 2D y 3D que vamos a utilizar aunque no es el único exis-
tente. Matplotlib tiene multitud de librerías de las cuales nosotros, por semejanza a Matlab, utilizaremos
pyplot. Recomendamos al lector visitar http://matplotlib.sourceforge.net/, donde puede encontrar multitud de
programas y ejemplos de como hacer dibujos con Matplotlib. La documentación oficial se encuentra en
http://matplotlib.sourceforge.net/contents.html no obstante, a lo largo de esta sección veremos algunas de las fun-
ciones más interesantes.
Para poder utilizar el módulo Matplotlib en nuestros programas primero debemos importarlo. Por comodidad los
módulos de nombre más extenso suelen renombrarse para optimizar su importación. En nuestro caso vamos a
importarlo como plt incluyendo en el inicio de nuestro programa la siguiente línea de código:
import matplotlib.pyplot as plt
En la Figura 3 podemos ver que si escribimos esta línea de comando en una consola, con solo poner plt se
nos muestra en pantalla la ayuda para este módulo. Si además tras plt añadimos un punto, se nos muestra un
4.3. Matplotlib 57
PIMCD2013-python, Publicación 1.0
Figura 4.2: Figura 2. Ejemplo de gráfica realizada con la librería pyplot de Matplotlib (DOE de fase).
desplegable con todas las funciones en este módulo. Si pinchamos en una de ellas, al igual que antes, se nos abre
una ventana de ayuda que nos indica el uso del comando.
como ya explicamos en temas anteriores también podemos importar matplotlib mediante la sentencia
from matplotlib import pyplot
El módulo Matplotlib (al igual que la mayoría de los módulos) es enorme y explicar todas y cada una de las
funciones nos llevaría demasiado tiempo así que analizaremos las más interesnates para un usuario principiante.
figure(num, figsize, dpi, facecolor, edgecolor, frameon) –> Crea una nueva figura. Se puede utilizar sin argu-
mentos. Devuelve un identificador a la figura.
num = numeración de la figura, si num = None, las figuras se numeran automáticamente.
figsize = w, h tuplas en pulgadas. Tamaño de la figura
dpi = Resolución de la imagen en puntos por pulgada.
facecolor = Color del rectángulo de la figura.
edgecolor = Color del perímetro de la figura.
frameon = Si es falso, elimina el marco de la figura.
>>> figure(num = None, figsize = (8, 6), dpi = 80, facecolor = ’w’, edgecolor = ’k’)
subplot(numRows, numCols, plotNum) –> Permite incluir varias gráficas en una única figura.
numRows = Número de filas
Figura 4.3: Figura 3. Importación del módulo matplotlib como plt en una consola interactiva de Eclipse.
4.3. Matplotlib 59
PIMCD2013-python, Publicación 1.0
plot(x, y, linestyle, linewidth, marker) –> Permite incluir varias gráficas en una única figura.
x = Abcisas.
y = Ordenadas. Tanto x como y pueden ser abcisas tuplas, listas o arrays. La única condición es que el
tamaño de ambas debe ser el mismo ya que en caso contrario python nos devolverá un fallo de tipo dimesión.
También se puede hacer una gráfica sin especificar la coordenada x.
linestyle = color y tipo de dibujar la gráfica. Por ejemplo ‘k- -‘
linewidth = ancho de línea.
marker = Marcador.
por ejemplo,
>>> plt.plot(x, y, ’k--’, linewidth = 2)
Dibuja la curva y(x) en trazo discontinuo de color negro con ancho de línea de tamaño 2. La función plot devuelve
una lista. El número de elementos depende del número de plots realizados. El tipo retornado para cada elemento
de la lista es matplotlib.lines.Lines2D. Analizando cada elemento se puede extraer información que caracteriza al
gráfico (formato de marcador, colores usados,...).
>>> from matplotlib import pyplot
>>> x = range(10)
>>> y = x
>>> a = pyplot.plot(x,y)
>>> len(a)
1
>>> a[0].get_color()
’b’
>>> a[0].get_ls()
’-’
>>> a[0].get_marker()
’None’
‘w’ Blanco
También se puede escribir el color de las siguientes formas: nombres (‘green’); cadenas hexadecimales
(‘#008000’); tuplas con convención RGB (0,1,0); intensidades de escala de grises (‘0.8’).
Marcadores
Los tipos principales son:
[ ‘+’ | ‘*’ | ‘,’ | ‘.’ | ‘1’ | ‘2’ | ‘3’ | ‘4’ | ‘<’ | ‘>’ | ‘D’ | ‘H’ | ‘^’ | ‘_’ | ‘d’ | ‘h’ | ‘o’ | ‘p’ | ‘s’ | ‘v’ | ‘x’ ].
Fillstyle Relleno de símbolo elegido para representar cada dato.
[‘full’ | ‘left’ | ‘right’ | ‘bottom’ | ‘top’]
show() Presenta las figuras en pantalla mediante una ventana en la que podemos interactuar. Este comando se
debe añadir siempre que necesitemos obervar nuestra gráfica ya que sino python realizará el cálculo pero no
presentará la imagen. Lo usual es ponerlo al final del programa posibilitando aasí la llamada a todas las figuras
que se encuentren en el mismo.
>>> plt.show()
legend(labels, loc) Coloca una leyenda. Cuando se presentan varías curvas simultáneamente este comando identi-
fica cada una. Puede utilizarse sin argumentos si en cada función plot (o equivalente) se ha incluido el argumento
‘label’, que es un texto para identificar la curva.
>>> plt.legend( (’Etiqueta1’, ’Etiqueta2’, ’Etiqueta3’), loc = ’upper left’)
axis() Establece u obtiene las propiedades de los ejes. Podemos establecer el rango de coordenadas en x e y que
queremos mostrar en el gráfico. Del mismo modo, podemos seleccionar la relación de aspecto entre las coordena-
das x e y.
axis(), devuelve los límites de los ejes ([xmin, xmax, ymin, ymax])
axis(v), establece los valores límites de los ejes a v = [xmin, xmax, ymin, ymax]
axis(‘off’), elimina líneas de ejes y etiquetas
axis(‘equal’), cambia los límites de x e y para que los incrementos de x e y tengan la misma longitud (un
círculo parece un círculo)
axis(‘scaled’), cambia las dimensiones del plot para conseguir la misma longitud de intervalo en x e y.
axis(‘tight’), cambia los ejes para que se muestren todos los datos.
axhline(y, xmin, xmax) Con esos valores de los parámetros predeterminados establecidos, la línea se ajusta al
rango representado por los correspondientes ejes.
y, array con los datos
4.3. Matplotlib 61
PIMCD2013-python, Publicación 1.0
hold() Si el parámetro es True, podemos poner más curvas en la misma gráfica. Si el parámetro es False, entonces
al escribir una nueva curva, las anteriores se borran.
grid() Establece la malla a True (visible) o False (oculta).
>>> plt.grid(True)
>>> plt,grid(color = ’r’, linestyle = ’’, linewidth = 2)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Representamos
plt.plot(x,y)
# Mostramos en pantalla
plt.show()
1.0
0.5
0.0
0.5
1.00 2 4 6 8 10
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_matplotlib_grafica_completa.py
# Propósito: Aprender a realizar una gráfica lo más completa posible
#
# Origen: Propio.
# Autor: Luis Miguel Sánchez-Brea,José María Herrera-Fernandez
#
# Creación: 18 de Septiembre de 2013
# Historia:
#
# Dependencias: numpy, matplotlib
# Licencia: GPL
#----------------------------------------------------------------------
"""
Descripción: Doble gráfica senoidal donde se muestran la mayoría de los complementos
que se pueden añadir a una gráfica
"""
4.3. Matplotlib 63
PIMCD2013-python, Publicación 1.0
periodo = 0.5
# Creamos la figura
plt.figure()
# Añadimos la leyenda
plt.legend(loc = 2)
# Añadimos texto
plt.text(x = 1, y = 0.0, s = u’T = 0.05’, fontsize = 24)
# Añadimos la rejilla
plt.grid(True)
plt.grid(color = ’0.5’, linestyle = ’--’, linewidth = 1)
# Añadimos el título
plt.title(’(a)’,fontsize = 28, color = ’0.75’, verticalalignment = ’baseline’, horizontalalignment
# Guardamos
plt.savefig(’plotCompleta.png’)
# Mostramos en pantalla
plt.show()
(a)
y1
y2
0.5
T = 0.05
y(µm)
0.0
0.5
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_matplotlib_subplot.py
# Propósito: Aprender a mostrar varías gráficas separadas en una figura
#
# Origen: Propio.
# Autor: Luis Miguel Sánchez-Brea,José María Herrera-Fernandez
#
# Creación: 18 de Septiembre de 2013
# Historia:
#
# Dependencias: numpy, matplotlib
# Licencia: GPL
#----------------------------------------------------------------------
"""
Descripción: En este ejemplo se muestra como realizar la presentanción de varías
gráficas individuales en una misma figura mediante "subplot"
"""
4.3. Matplotlib 65
PIMCD2013-python, Publicación 1.0
periodo = 2
# Creamos la figura
plt.figure()
# Primera gráfica
plt.subplot(2,2,1)
plt.plot(x, y,’r’)
# Segunda gráfica
plt.subplot(2,2,2)
plt.plot(x, y,’g’)
# Tercera gráfica
plt.subplot(2,2,3)
plt.plot(x, y,’b’)
# Cuarta gráfica
plt.subplot(2,2,4)
plt.plot(x, y,’k’)
# Mostramos en pantalla
plt.show()
1.0 1.0
0.5 0.5
0.0 0.0
0.5 0.5
1.00 2 4 6 8 10 1.00 2 4 6 8 10
1.0 1.0
0.5 0.5
0.0 0.0
0.5 0.5
1.00 2 4 6 8 10 1.00 2 4 6 8 10
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_matplotlib_imshow.py
# Propósito: Aprender a realizar una gráfica en 2D
#
# Origen: Propio.
# Autor: Luis Miguel Sánchez-Brea,José María Herrera-Fernandez
#
# Creación: 18 de Septiembre de 2013
# Historia:
#
# Dependencias: numpy, matplotlib
# Licencia: GPL
#----------------------------------------------------------------------
"""
Descripción: En este ejemplo se muestra como realizar una gráfica en dos dimensiones
mediante imshow
"""
# Representamos
plt.imshow(fxy);
# Mostramos en pantalla
plt.show()
4.3. Matplotlib 67
PIMCD2013-python, Publicación 1.0
0 1.0
0.8
200 0.6
0.4
400 0.2
0.0
600 0.2
0.4
800 0.6
0.8
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
#----------------------------------------------------------------------
# Nombre: ejemplo_matplotlib_grafica_superficie3d.py
# Propósito: Aprender como leer una image y representar una grafica en 3D.
#
# Origen: Propio .
# Autor: José María Herrera-Fernandez
#
# Creación: 18 de Septiembre de 2013
# Historia:
#
# Dependencias: scipy, mpl_toolkits, matplotlib, numpy
# Licencia: GPL
#----------------------------------------------------------------------
"""
Descripción: Ejemplo de cómo usar la función integrate para realizar integrales
numéricas en python.
"""
# Añado etiquetas
plt.title(’Imagen que usaremos de superficie’)
plt.xlabel(u’# de píxeles’)
plt.ylabel(u’# de píxeles’)
# Añadimos etiquetas
plt.title(u’Imagen sobre una grafica 3D’)
plt.xlabel(’Eje x’)
plt.ylabel(’Eje y’)
# Mostramos en pantalla
plt.show()
4.3. Matplotlib 69
PIMCD2013-python, Publicación 1.0
0
Imagen que usaremos de superficie
20
40
# de píxeles
60
80
100
1.0
0.5
0.0
0.5
6
4 1.0
2 6
Eje 0 4
x 2
2 0 y
4 4
2 Eje
6 6
5.1 Introducción
E = E1 + E2
también es una solución. Sin embargo, el campo no es un parámetro observable, sino que lo es el promedio
temporal del vector de Poynting, que se define como
1
S≡ E ∧ B,
𝜇0
con tiene unidades de [S] = 𝑊/𝑚2 . Al particularizar al caso de ondas armónicas planas tenemos
√︂
1 𝜀0 2
⟨S⟩ = |E| u𝑘 ,
2 𝜇0
: 𝑚𝑎𝑡ℎ : ‘(𝑡, 𝑟)‘
71
PIMCD2013-python, Publicación 1.0
5.2 Programación
Debido a la sencillez conceptual de las interferencias, las utilizaremos para analizar los distintos tipos de pro-
gramación que se puede realizar con Python, como es la programación con scripts (todo el código en un mismo
archivo lineal), la programación funcional (se escriben funciones que simplifican lo cálculos) y la programación
con clases (donde funciones y datos están íntimamente relacionados). Finalmente veremos un procedimiento sen-
cillo para desarrollar vídeos, simulando un interferómetro de Michelson.
tamano=100*um
x=sp.linspace(-tamano,tamano, 256)
y=sp.linspace(-tamano,tamano, 256)
X,Y=sp.meshgrid(x,y)
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: onda plana
#-------------------------------------
import scipy as sp
import matplotlib.pyplot as plt
import sys
sys.path.append(’../’)
IDfig = plt.figure()
IDax = IDfig.add_subplot(111)
extension = [x.min(), x.max(), y.min(), y.max()]
angulo = sp.angle(u) #/ sp.pi
angulo[angulo == 1] = -1
IDimagen = plt.imshow(angulo, interpolation = ’bilinear’, aspect = ’auto’,origin = ’lower’
plt.xlabel("$x (\mu m)$", fontsize = 20)
plt.ylabel("$y (\mu m)$", fontsize = 20)
titulo="$A =$ %4.2f $\phi =$ %4.2f $^{0}, \Theta =$ %4.2f $^0$" %(A, phi*180/sp.pi, teta
plt.suptitle(titulo, fontsize = 20)
plt.axis(’scaled’)
plt.axis(extension)
plt.colorbar()
IDimagen.set_cmap("RdBu")
plt.clim(vmin = -sp.pi, vmax = sp.pi)
1.6
50
0.8
y(µm)
0 0.0
0.8
50
1.6
100 2.4
100 50 0 50 100
x(µm)
5.2. Programación 73
PIMCD2013-python, Publicación 1.0
1.6
50
0.8
y(µm)
0 0.0
0.8
50
1.6
100 2.4
100 50 0 50 100
x(µm)
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: onda esferica
#-------------------------------------
import scipy as sp
import matplotlib.pyplot as plt
um=1
#Onda esferica
u = A * sp.exp(-1.j * sp.sign(z0) * k * Rz) / Rz
IDfig = plt.figure()
IDax = IDfig.add_subplot(111)
extension = [x.min(), x.max(), y.min(), y.max()]
angulo = sp.angle(u)
angulo[angulo == 1] = -1
IDimagen = plt.imshow(angulo, interpolation = ’bilinear’, aspect = ’auto’,origin = ’lower’
plt.xlabel("$x (\mu m)$", fontsize = 20)
plt.ylabel("$y (\mu m)$", fontsize = 20)
titulo="fase A = %4.2f z0 = %4.2f mm" %(A, z0/1000*um)
plt.suptitle(titulo, fontsize = 20)
plt.axis(’scaled’)
plt.axis(extension)
plt.colorbar()
IDimagen.set_cmap("RdBu")
plt.clim(vmin = -sp.pi, vmax = sp.pi)
plt.show()
1.6
50
0.8
y(µm)
0 0.0
0.8
50
1.6
100 2.4
100 50 0 50 100
x(µm)
5.2. Programación 75
PIMCD2013-python, Publicación 1.0
1.6
50
0.8
y(µm)
0 0.0
0.8
50
1.6
100 2.4
100 50 0 50 100
x(µm)
fase A = 1.00 z0 = 5.00 mm
100 2.4
1.6
50
0.8
y(µm)
0 0.0
0.8
50
1.6
100 2.4
100 50 0 50 100
x(µm)
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Emision de una onda esferica
#-------------------------------------
wavelength=0.6328*um
k = 2 * sp.pi / wavelength
c= 299792.458 *m/s
w=c*k
#area de visualizacion
tamano=1.25*um
x=sp.linspace(-tamano, tamano, 250)
z=sp.linspace(-tamano, tamano, 250)
X,Z=sp.meshgrid(x, z)
#calculo de la intensidad
R = sp.sqrt(X** 2 + Z ** 2)
E_theta=sp.cos(k*R)/R
intensidad = abs(E_theta) ** 2
intensidad=sp.log(intensidad+1)
#fase=sp.angle(E_theta)
#tiempo
t=sp.linspace(0,50000*fs,1001)
#bucle
i=0
def updatefig(*args):
global t, i
E_theta=sp.cos((k*R-w*t[i]))/R
intensidad = abs(E_theta) ** 2
intensidad=sp.log(intensidad+1)
#fase=sp.angle(E_theta)
im.set_array(intensidad)
titulo="t = %4.0f fs" %(t[i]/fs)
plt.title(titulo)
manager.canvas.draw()
i += 1
if i==len(t):
return False
return True
5.2. Programación 77
PIMCD2013-python, Publicación 1.0
gobject.idle_add(updatefig)
plt.show()
50
100
150
200
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: haz gaussiano
#-------------------------------------
import scipy as sp
import matplotlib.pyplot as plt
import sys
sys.path.append(’../’)
#Definicion de la amplitud
u = A * sp.exp(-X ** 2 / (2 * wx ** 2) - Y** 2 / (2 * wy ** 2))
intensidad=abs(u)**2
IDfig = plt.figure()
IDax = IDfig.add_subplot(111)
extension = [x.min(), x.max(), y.min(), y.max()]
IDimagen = plt.imshow(intensidad ,origin = ’lower’, extent = extension)
plt.xlabel("$x (\mu m)$", fontsize = 20)
plt.ylabel("$y (\mu m)$", fontsize = 20)
plt.axis(’scaled’)
plt.axis(extension)
plt.colorbar()
IDimagen.set_cmap("gist_heat")
1.0
400 0.9
0.8
200 0.7
0.6
y(µm)
0 0.5
0.4
200 0.3
0.2
400 0.1
5.2. Programación 79
PIMCD2013-python, Publicación 1.0
1.0
400 0.9
0.8
200 0.7
0.6
y(µm)
0 0.5
0.4
200 0.3
0.2
400 0.1
Veamos ahora el proceso de interferencia de 2 haces de Gauss. Estos hace se pueden definir como
𝜌2 𝜌2
(︂ )︂ (︂ )︂
𝜔0
𝐸(𝑥, 𝑦, 𝑧) = 𝐴0 exp − 2 exp 𝑖𝑘𝑧 + 𝑖𝑘 − 𝑖𝜁(𝑧)
𝜔(𝑧) 𝜔 (𝑧) 2𝑅(𝑧)
donde
𝑧0 𝜆
𝜔02 =
𝜋
√︃ (︂ )︂2
𝑧
𝜔(𝑧) = 𝜔0 1+
𝑧0
[︂ ]︂
𝑧
𝑅(𝑧) = 𝑧 1 + ( )2
𝑧0
Este tipo de haces se puede programar fácilmente. Asumamos que estamos en el foco del haz z=0 y los haces están
rotados. Entonces también se van a producir interferencias entre los haces.
Polarización
81
PIMCD2013-python, Publicación 1.0
6.1 Introducción
Desde un punto de vista clásico, la luz es una onda electromagnética constituida por dos campos, eléctrico y
magnético oscilando normales a la dirección de propagación y normalmente entre sí. Bajo esta definición se llama
polarización de la luz a la trayectoria del vector campo eléctrico. Si la trayectoria de la luz es caótica se dice que
la luz está despolarizada, así mismo si la trayectoria está bien definida hablamos de estados de polarización o
también llamados estados puros.
La polarización de la luz está intrínsecamente ligada al concepto de onda plana (ver sección, Fuentes de luz-Onda
plana).
Andras Szilagyi ha realizado un módulo interactivo muy interesante para el análisis de la polarizacion:
emanim12.py. Se puede encontrar en http://www.enzim.hu/~szia/emanim for more information.
En la siguiente Figura se muestra un ejemplo de utilización de este módulo. Tiene un menú y botones para selec-
cionar el tipo de análisis de polarización y parámetros utilizados
La luz polarizada puede cambiar de estado al atravesar distintos elementos ópticos. La caracterización y modelado
de estos medios resulta crítica en óptica. El cálculo de Jones permite no solo definir estados de polarización sino
82 Capítulo 6. Polarización
PIMCD2013-python, Publicación 1.0
además, representar los elementos ópticos mediante matrices, tales que el vector de Jones a la salida del sistema
sea la acción de la matriz del medio sobre un vector de Jones.
Rotador
Un rotador es un sistema capaz de rotar el vector de Jones pero no modificar su estado. Por ello están representados
por matrices de rotación. Para su implementación basta con definir la matriz
(︂ )︂
cos 𝜃 sin 𝜃
,
− sin 𝜃 cos 𝜃
Polarizador lineal
Los polarizadores lineales son elementos ópticos tales que independientemente del estado de polarización a la
entrada transforman dicho estado en luz linealmente polarizada con acimut 𝜃. Donde 𝜃 representa el ángulo del
eje del polarizador. La matriz de Jones de un polarizador lineal viene representada por
cos2 𝜃
(︂ )︂
sin 𝜃 cos 𝜃
,
sin 𝜃 cos 𝜃 sin2 𝜃
Polarizador Retardador
Un retardador lineal es un elemento óptico compuesto por un material birrefringente capaz de introducir un re-
tardo entre las componentes paralela y perpendicular de la luz incidente. Está constituido por dos ejes o también
llamadas líneas neutras con índices de refracción asociados ordinario y extraordinario, la orientación de los ejes y
el espesor determinan la acción de la lámina sobre la luz incidente. El retardo se introduce fruto de la diferencia
de camino óptico asociada a cada eje. La matriz de Jones asociada a un retardador viene dada por
Lámina Lambda/4
Las láminas retardadoras hacen uso de la birrefringencia de la luz. Una lámina de cuarto de onda introduce un
retardo de 𝜋/2 entre las componentes del vector de Jones de la luz incidente sobre la lámina. La matriz de Jones
del retardador de cuarto de onda es
(︂ )︂
1 + 𝑖 cos 2𝜃 𝑖 sin 2𝜃
,
𝑖 sin 2𝜃 1 − 𝑖 cos 2𝜃
Lámina Lambda/2
Las láminas de media onda, alteran el sentido de giro del vector luz, derechas a izquierda o viceversa. Por tanto
los estados linealmente polarizados son invariantes bajo estos elementos ópticos, mientras que si se incide sobre
una lámina de media onda con luz circular a derechas, a la salida mantiene la circularidad pero el sentido de giro
será levógiro. Idem para estados elípticamente polarizados. La matriz de Jones del retardador de media onda es
(︂ )︂
cos 2𝜃 sin 2𝜃
,
sin 2𝜃 − cos 2𝜃
Considérese una onda plana monocromática de frecuencia 𝜔, vector de onda ⃗𝑘, y amplitud 𝐸0 representada como
⃗
𝐸𝑥 = 𝐸0𝑥 𝑒𝑖𝑘⃗𝑟−𝜔𝑡+𝛿𝑥 ,
⃗
𝐸𝑦 = 𝐸0𝑦 𝑒𝑖𝑘⃗𝑟−𝜔𝑡+𝛿𝑥 .
Es posible definir esta señal únicamente con la parte espacial. Así denotando el desfase como 𝛿 = 𝛿𝑦 − 𝛿𝑥 se
escribe
(︂ )︂
⃗ 𝐸0𝑥
𝐸= .
𝐸0𝑦 𝑒𝑖𝛿
Este vector recibe el nombre de vector de Jones. Obsérvese como este vector representa la proyección trayectoria
del campo eléctrico sobre un plano perpendicular a la dirección de propagación. Así se puede probar como el
conjunto de trayectorias posibles de acuerdo a estos vectores están definidas por la ecuación
𝑥2 𝑦2 𝑥𝑦 cos 𝛿
2 + 2 −2 = sin2 𝛿.
𝐸0𝑥 𝐸0𝑦 𝐸0𝑥 𝐸0𝑦
84 Capítulo 6. Polarización
PIMCD2013-python, Publicación 1.0
Esta ecuación representa una elipse, la llamada elipse de polarización y de ella derivan el conjunto de estados
puros. De este modo, si 𝛿 = 𝑛𝜋 con 𝑛, entero, o si 𝐸0𝑥 = 0 o 𝐸0𝑦 = 0, la elipse de polarización degenera en una
recta, y se dice que el estado de la luz es linealmente polarizado, así mismo, si 𝐸0𝑥 = 𝐸0𝑦 y 𝛿 = ±𝜋/2, la elipse
degenera en una circunferencia, y por tanto la luz es circularmente polarizada. En cualquier otro caso distinto de
estos dos la luz se dice que posee polarización elíptica.
Luz Lineal
Un vector de Jones unitario de luz linealmente polarizada es siempre de la forma
(︂ )︂
⃗ cos 𝛼
𝐽= .
sin 𝛼
Donde 𝛼 representa el azimut del estado de luz, es decir, la inclinación del vector campo eléctrico. luzLineal
implementa este vector.
def luzLineal(alfa = 0):
"""Luz linealmente polarizada
- alfa, angulo en el cual esta polarizada
"""
#Definicion del vector de Jones
return sp.matrix([[ cos(alfa)], [sin(alfa)]])
Luz Circular
Bajo la condición 𝛿 = ±𝜋/2 con 𝐸0𝑥 = 𝐸0𝑦 se obtienen los vectores de Jones
El doble signo denota el sentido de giro del vector campo eléctrico, si es positivo, entonces la luz será circularmente
polarizada a derechas o dextrógira. En caso contrario se dice que es circularmente polarizada a izquierdas o
levógira. La elección entre circular a derechas o izquierdas se especifica mediante una d o una i en el argumento
de la función luzCircular
def luzCircular(tipo = ’d’):
"""Luz circularmente polarizada
- tipo, ’d’: derecha ’i’: izquierda
"""
#Definicion del vector de Jones a dextrogiro o levogiro
if tipo == ’d’:
return sp.matrix([[1], [1j]]) / sp.sqrt(2)
elif tipo == ’i’:
return sp.matrix([[1], [-1j]]) / sp.sqrt(2)
Luz Elíptica
Si el vector de Jones no es ninguno de los dos casos anteriores, lineal o circular, entonces es necesariamente
elípticamente polarizado. Su definición como función de Python debe emplear la forma más general del vector de
Jones,
(︂ )︂
𝑎
.
𝑏𝑒𝑖𝛿
La intensidad de la luz no es más que el módulo del vector de Jones. La función intensidad cumple este cometido
def intensidad(vectorPolarizacion):
#Calculo de la intensidad
return float(abs(vectorPolarizacion[0]) ** 2 + abs(vectorPolarizacion[1]) ** 2)
La polarización de la luz se puede ver afectada al atravesar esta distintos elementos ópticos. El formalismo de Jones
constituye una representación tanto de los elementos ópticos, mediante matrices 2x2, como de estados puros de
polarización, mediante vectores. De este modo, el estado de polarización a la salida de un elemento óptico queda
determinado por la acción de la matriz de Jones del elemento sobre el vector de Jones que representa el estado de
polarización incidente.
Así supónganse los siguientes ejemplos
Polarizador lineal
Un haz de luz linealmente polarizado según el eje x incide sobre un polarizador cuyo eje está alineado con el eje
y. Obviamente no hay transmisión de luz, por ello el vector de Jones a la salida del polarizador debe ser el vector
nulo, y en consecuencia la intensidad de la luz es cero. En efecto
(︂ )︂ (︂ )︂ (︂ )︂
0 0 1 0
=
0 1 0 0
(︂ )︂ (︂ )︂ (︂ )︂
1 0 1 1
=
0 −1 𝑖 −𝑖
(︂ )︂ (︂ )︂ (︂ )︂
1 𝑖 1 1
=
𝑖 1 0 𝑖
La ley de Malus establece que la intensidad de la luz a la salida de un sistema de dos polarizadores es directamente
proporcional a cos2 𝜃 siendo 𝜃 el ángulo comprendido entre los ejes de los polarizadores. La demostración es
sencilla, basta considerar un estado de luz lineal incidiendo sobre un polarizador, y calcular el vector de Jones a
86 Capítulo 6. Polarización
PIMCD2013-python, Publicación 1.0
la salida, el módulo cuadrado de este vector de Jones posee una dependencia directamente proporcional a cos2 𝜃.
Este hecho se muestra en el siguiente código.
"""Ley de Malus"""
#Importamos modulos scipy, sys y polarizacion
import scipy as sp
import sys
sys.path.append(’../’)
from polarizacion import *
#haz incidente
haz=luzLineal(alfa=0*grados)
P1 = polarizadorLineal(theta = 0*grados)
#Representacion de la intensidad
plt.figure()
plt.plot(angulos/grados,I,’k’,lw=2)
plt.xlabel(r’$\theta$ (grados)’,fontsize=24)
plt.ylabel(’Intensidad transmitida’,fontsize=24)
plt.xlim([0,angulos[-1]/grados])
plt.show()
1.0
Intensidad transmitida
0.8
0.6
0.4
0.2
El efecto Zenón es el paso de luz a través de un sistema de 𝑁 polarizadores. Si bien se extingue la luz bajo cuando
dos polarizadores posicionan sus ejes perpendicularmente, bajo otros ángulos la luz atraviesa el sistema. Así, si se
disponen N polarizadores con ángulo entre sus ejes 360/𝑁 aumentar la transmitancia al aumentar el número de
polarizadores.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#----------------------------------------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#----------------------------------------------------------------------
"""
Efecto Zenon, n polarizadores rotados igual entre si.
"""
import scipy as sp
import sys
sys.path.append(’../’)
from polarizacion import *
#Representacion de la intensidad
plt.figure()
plt.plot(numPolarizadores, intensidad, ’ko’)
plt.xlabel(’# polarizadores’, fontsize = 20)
plt.ylabel(’$I_{transmitida}$’, fontsize = 24)
plt.title(’efecto zenon’, fontsize = 24)
if __name__ == ’__main__’:
efectoZenon(numMaxPolarizadores = 75)
plt.show()
88 Capítulo 6. Polarización
PIMCD2013-python, Publicación 1.0
0.4
Itransmitida
0.3
0.2
0.1
0.00 10 20 30 40 50 60 70 80
# polarizadores
90 Capítulo 6. Polarización
CAPÍTULO 7
Interacción radiación-materia
7.1 Introducción
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Ejemplos de difraccion en campo lejano
#-------------------------------------
import scipy as sp
import matplotlib.pyplot as plt
m = 1 #metros
kg = 1 #kilogramo
sg = 1 #segundos
V = 1 #voltio
91
PIMCD2013-python, Publicación 1.0
C = 1 #culombios
Hz = 1 / sg #hertzio
nm = 1e-9 * m
- Precondicion: g<<w0
- Entradas:
* w0: frecuencia de resonancia
* gamma: amortiguamiento
- Salidas:
* r: distancia compleja
"""
#distancia electron-nucleo
r = (qe / me) * E0 / (w ** 2 - w0 ** 2 - 1.j * gamma * w)
#diversos dibujos
#distancia
plt.figure(figsize = (11, 9), facecolor = ’w’, edgecolor = ’k’)
plt.subplot(221)
plt.plot(w, abs(r) / nm, ’k’, linewidth = 2)
plt.xlabel(’$\omega \; (Hz)$’, fontsize = 18)
plt.ylabel(’$r\;(nm)$’, fontsize = 18)
#desfase
plt.subplot(222)
plt.plot(w, sp.angle(r) * 180 / sp.pi, ’k’, linewidth = 2)
plt.xlabel(’$\omega \; (Hz)$’, fontsize = 18)
plt.ylabel(’$\phi \;(grados)$’, fontsize = 18)
#parte real
plt.subplot(223)
plt.plot(w, sp.real(r), ’k’, linewidth = 2)
plt.xlabel(’$\omega \; (Hz)$’, fontsize = 18)
plt.ylabel(’$Re(r)$’, fontsize = 18)
#parte imaginaria
plt.subplot(224)
plt.plot(w, sp.imag(r), ’k’, linewidth = 2)
plt.xlabel(’$\omega \; (Hz)$’, fontsize = 18)
plt.ylabel(’$Im(r)$’, fontsize = 18)
plt.show()
1.8 0
1.6 20
1.4 40
1.2 60
φ (grados)
r (nm)
1.0 80
0.8 100
0.6 120
0.4 140
0.2 160
0.00.0 0.5 1.0 1.5 2.0 1800.0 0.5 1.0 1.5 2.0
ω (Hz) 1e9+9.99999e14 ω (Hz) 1e9+9.99999e14
1.0 1e 9 0.0 1e 9
0.2
0.5 0.4
0.6
0.8
Im(r)
Re(r)
0.0
1.0
1.2
0.5 1.4
1.6
1.00.0 0.5 1.0 1.5 2.0 1.80.0 0.5 1.0 1.5 2.0
ω (Hz) 1e9+9.99999e14 ω (Hz) 1e9+9.99999e14
7.2 Dipolo
Una carga eléctrica oscilante genera un campo electromagnético. En el caso de un movimiento sinusoidal de
frecuencia w, el campo generado resulta
−𝑞𝑟𝑜 𝑘 3 2 2𝑖
𝐸𝑟 = cos 𝜃( − )𝑒𝑖(𝑘𝑅−𝜔𝑡)
4𝜋𝜀𝑜 (𝑘𝑅)3 (𝑘𝑅)2
−𝑞𝑟𝑜 𝑘 3 1 𝑖 1 𝑖(𝑘𝑅−𝜔𝑡)
𝐸𝜃 = sin 𝜃( − − )𝑒
4𝜋𝜀𝑜 (𝑘𝑅)3 (𝑘𝑅)2 𝑘𝑅
𝐸𝜑 = 0
Esto es lo que se ha programado en las siguientes figuras, para el caso de un plano y para un instante de tiempo
t=0.
7.2. Dipolo 93
PIMCD2013-python, Publicación 1.0
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Emision del dipolo
#-------------------------------------
import scipy as sp
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.cm as cm
wavelength=0.6328*um
k = 2 * sp.pi / wavelength
c= 299792.458 *m/s
w=c*k
q=1; r0=1; epsilon0=1; #normalizacion
#area de visualizacion
tamano=2.5*um
x=sp.linspace(-tamano/2, tamano/2, 250)
z=sp.linspace(-tamano/2, tamano/2, 250)
X,Z=sp.meshgrid(x, z)
#calculo de la intensidad
R = sp.sqrt(X** 2 + Z ** 2)
sinTheta=X/R
E_theta=q*r0*k**2/(4*sp.pi*R*epsilon0)*sinTheta*sp.cos((k*R))
intensidad = abs(E_theta) ** 2
intensidad=sp.log(intensidad+1)
50
100
150
200
contourf contour
4 4
2 2
0 0
2 2
4 4
4 2 0 2 4 4 2 0 2 4
0.0 1.5 3.0 4.5 6.0 7.5 9.0 10.512.0 1.5 3.0 4.5 6.0 7.5 9.0 10.5
Como la fluctuación es temporal, en los siguientes archivos se muestra este movimiento. Uno de ellos genera una
7.2. Dipolo 95
PIMCD2013-python, Publicación 1.0
50
100
150
200
7.3 Resonancia
El efecto que tiene la luz sobre el movimiento de los electrones depende fuertemente de la frecuencia de la
onda electromagnética incidente en comparación con la frecuencia de resonancia del electrón en el átomo. En la
siguiente figura se muestra cuál es el desplazamiento máximo producido en el átomo en funcion de los parámetros
del átomo y de la frecuencia de la luz incidente.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- coding: cp1252 -*- " Codigo para poder usar tildes"
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
import scipy as sp
import matplotlib.pyplot as plt
m = 1 #metros;
kg = 1 #kilogramo
sg = 1 #segundos
V = 1 #voltio
C = 1 #culombios
Hz = 1 / sg #hertzio
nm = 1e-9 * m
qe = -1.602176565 * 10 ** -19 * C #carga del electron
me = 9.10938291 * 10 ** -31 * kg #masa del electron
1.8 r 0 desfase
1.6 20
1.4 40
1.2 60
φ (grados)
r (nm)
1.0 80
0.8 100
0.6 120
0.4 140
0.2 160
0.00.0 0.5 1.0 1.5 2.0 1800.0 0.5 1.0 1.5 2.0
ω (Hz) 1e9+9.99999e14 ω (Hz) 1e9+9.99999e14
7.3. Resonancia 97
PIMCD2013-python, Publicación 1.0
la densidad de energía espectral, i.e, la cantidad de energía electromagnética por unidad espectral que llega por
unidad de superficie
𝛾 2 /4
𝐽(𝜔) = 𝜀𝑜 𝑐|𝐸𝜔 |2 = 𝐽(𝜔𝑜 ) ,
𝛾 2 /4 + (𝜔 − 𝜔𝑜 )2
donde 𝛾 es el campo a la salida del elemento difra, 𝜔𝑜 es la frecuencia de resonancia y 𝜔 es la frecuencia del haz
incidente
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- coding: cp1252 -*- " Codigo para poder usar tildes"
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
import scipy as sp
import matplotlib.pyplot as plt
m = 1 #metros;
kg = 1 #kilogramo
sg = 1 #segundos
V = 1 #voltio
C = 1 #culombios
Hz = 1 / sg #hertzio
nm = 1e-9 * m
qe = -1.602176565 * 10 ** -19 * C #carga del electron
me = 9.10938291 * 10 ** -31 * kg #masa del electron
epsilon0=8.8541878176e-12
c=3e8*m/sg
#distancia electron-nucleo
J=epsilon0*c*abs(E0)**2/(gamma**2/4+(w-w0)**2)
#distancia
plt.figure(facecolor = ’w’, edgecolor = ’k’)
plt.plot(w, J, ’k’, linewidth = 2)
plt.xlabel(’$\omega \; (Hz)$’, fontsize = 18)
plt.ylabel(’$J(\omega)$’, fontsize = 18)
1.2 1e 12
1.0
0.8
J ( ω)
0.6
0.4
0.2
Esta sección eficaz de extinción la denotamos 𝜎𝑒 y su relación con la potencia extraída es la siguiente
⟨𝑃 ⟩ 𝑞2 𝛾𝜔 2
𝜎𝑒 = =
⟨|𝑆|⟩ 𝑚𝑐𝜀𝑜 (𝜔 2 − 𝜔𝑜2 )2 + 𝜔 2 𝛾 2
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- coding: cp1252 -*- " Codigo para poder usar tildes"
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
import scipy as sp
import matplotlib.pyplot as plt
m = 1; kg = 1; s = 1; V = 1; C = 1; Hz = 1 / s
nm = 1e-9 * m
qe = -1.602176565e-19 * C #carga del electron
me = 9.10938291e-31 * kg #masa del electron
epsilon0=8.8541878176e-12
c= 299792458*m/s
sigma=sigma_T*w**4/((w**2-w0**2)**2+w**2*gamma**2)
#distancia
plt.figure(facecolor = ’w’, edgecolor = ’k’)
plt.plot(w, sigma, ’k’, linewidth = 2)
plt.xlabel(’$\omega \; (Hz)$’, fontsize = 18)
plt.ylabel(’$\sigma(\omega)$’, fontsize = 18)
plt.title("Seccion eficaz de scattering", fontsize=24)
texto="radio = %4.4g m" %(sp.sqrt(sigma_T/sp.pi))
plt.text(w.min(),sigma.max()/2,texto,fontsize=16)
plt.tight_layout()
plt.show()
0.6
σ(ω)
0.4
0.2
8.1 Introducción
Los ejemplos que aquí se desarrollan son autocontenidos, es decir, no necesitan de librerías especificas excepto
las estándar. El objetivo de este tema es, por una parte, conocer la relación entre el modelo microscópico clásico
de interacción radiación-materia con el índice de refracción. Asimismo, se pretende analizar diversas expresiones
analíticas para el índice de refracción, conociendo las propiedades del índice de refracción tales como la absorción
y la dispersión.
Se compara las definiciones macroscópica y microscópica de la polarización. La primera se calcula a través las
ecuaciones macroscópicas de Maxwell.
P = 𝜀0 𝜒𝑒 E𝑚𝑎𝑐 ,
La segunda se calcula a partir de las trayectorias de los átomos analizadas en el átomo de Lorentz. Mediante esta
comparación se obtiene una ecuación para la constante dieléctrica en función de los parámetros microscópicos:
𝑁
𝑁 1 ∑︁
P=𝛼 E𝑚𝑖𝑐,𝑗 .
∆𝑉 𝑁 𝑗=1
⏟ ⏞
103
PIMCD2013-python, Publicación 1.0
Como también es conocido, se puede obtener el índice de refracción a partir de la constante dieléctrica
√︂
𝜀𝑔𝑒𝑛
𝑛𝑐 = .
𝜀0
En el caso de que no haya cargas libres, es decir, que el medio sea dieléctrico, el índice de refracción se puede
simplificar a
⎛ ⎞
2 ∑︁
𝑞 𝑓𝑗
𝜀 = 𝜀0 ⎝1 + 𝑁𝑉 ⎠,
𝑚𝜖0 𝑗 𝜔02 − 𝜔 2 − 𝑖𝛾𝑗 𝜔
donde la sumatoria representa los distintos tipos de resonancias que existen en el material.
Sacando las partes real e imaginaria se puede obtener el índice de refracción y de absorción.
2
𝑞 2 ∑︁ (𝜔0,𝑗 − 𝜔 2 )𝑓𝑗
𝑛 = 1 + 𝑁𝑉 2 − 𝜔 2 )2 + 𝛾 2 𝜔 ,
2𝑚𝜖0 𝑗 (𝜔0𝑗 𝑗
𝑞 2 ∑︁ 𝜔𝛾𝑗 𝑓𝑗
𝜅 = 𝑁𝑉 2 − 𝜔 2 )2 + 𝛾 2 𝜔 2 .
2𝑚𝜖0 𝑗 (𝜔0𝑗 𝑗
En el siguiente ejemplo se muestra el índice de refracción para dos ejemplos. En el primero se muestra el índice
de refracción debido solamente a una resonancia.
En el segundo ejemplo se muestra el índice de refracción cuando el medio tiene 3 resonancias. Se observa un
carácter acumulativo. Para bajas frecuencias, el índice de refracción no es la unidad, sino mayor, mientras que
para altas frecuencias, por encima de la resonancia, la luz no observa la materia y el índice de refracción es la
unidad.
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Ejemplos de difraccion en campo lejano
#-------------------------------------
import scipy as sp
import matplotlib.pyplot as plt
m = 1; kg = 1; sg = 1; V = 1; C = 1;
Hz = 1 / sg; nm = 1e-9 * m
q=1.6021765e-19*C
m=9.109382911e-31*kg
epsilon0=8.8541878176e-12
n_puntos = 1000
anchura = 20
w = sp.linspace(min(w0) - anchura * max(gamma), max(w0) + anchura * max(gamma), n_puntos)
#distancia electron-nucleo
n2=1
nc=sp.sqrt(n2)
n=sp.real(nc)
kappa=sp.imag(nc)
plt.figure()
#parte real
plt.subplot(211)
plt.plot(w, n, ’k’, linewidth = 2)
plt.xlabel(’$\omega \; (Hz)$’, fontsize = 18)
plt.xlim(max(0,w[0]),w[-1])
plt.ylabel(’$n$’, fontsize = 18)
plt.grid(True)
#parte imaginaria
plt.subplot(212)
plt.plot(w, kappa, ’k’, linewidth = 2)
plt.xlabel(’$\omega \; (Hz)$’, fontsize = 18)
plt.xlim(max(0,w[0]),w[-1])
plt.ylabel(’$\kappa$’, fontsize = 18)
plt.grid(True)
plt.tight_layout()
#ejemplo de 1 resonancia
indice_refraccion(Nv=1e18, f=(1,), w0 = (1e13 * Hz,), gamma = (1e8 * Hz,))
#muchas resonancias
f=(0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1)
w0= sp.array([1.0,1.2,1.4,1.6,1.8,2.0,2.2,2.4,2.6,2.8])*4e14*Hz
gamma= sp.array([1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0])*1e13*Hz
indice_refraccion(Nv=1e26, f=f, w0 = w0, gamma = gamma)
plt.show()
1.8
1.6
1.4
1.2
n
1.0
0.8
0.6
0.40.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0
ω (Hz) 1e9+9.998e12
1.2
1.0
0.8
0.6
κ
0.4
0.2
0.00.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0
ω (Hz) 1e9+9.998e12
3.5
3.0
2.5
2.0
n
1.5
1.0
0.5
0.0 2.0 2.5 3.0 3.5 4.0
ω (Hz) 1e14
3.5
3.0
2.5
2.0
κ
1.5
1.0
0.5
0.0 2.0 2.5 3.0 3.5 4.0
ω (Hz) 1e14
3.0
2.5
2.0
1.5
n
1.0
0.5
0.00.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4
ω (Hz) 1e15
2.0
1.5
1.0
κ
0.5
0.00.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4
ω (Hz) 1e15
La ecuación anteriormente descrita es interesante, pero a la hora de trabajar con ella es mejor realizar algún tipo
de aproximación. En primer lugar porque en Óptica se suele trabajar con longitudes de onda y no con frecuencias.
La segunda es para hacer casar las ecuaciones de la dispersión de materiales, conocidas previamente y utilizadas
en la óptica geométrica.
Una de ellas es la ec. de Sellmeier.
𝑏𝜆2 𝑐𝜆2
𝑛2 (𝜆) − 1 = 𝑎 + + + ...
𝜆2 − 𝜆21 𝜆2 − 𝜆22
Se pueden encontrar los parámetros en muchas páginas web. Por ejemplo, hemos utilizado los siguientes materia-
les bastante comunes
Fused_Silica_B=(6.96166300E-01, 4.07942600E-01, 8.97479400E-01)
Fused_Silica_C=(4.67914826E-03, 1.35120631E-02, 9.79340025E01)
Veamos algunas de las gráficas y el código para programarlos. Este parámetro se denomina dispersión cromática.
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Ejemplos de difraccion en campo lejano
#-------------------------------------
import scipy as sp
import matplotlib.pyplot as plt
wavelength=sp.linspace(0.4, 1 ,1000)
i=2
n2=sp.zeros_like(wavelength)+1
for bi,ci in zip(B,C):
n2=n2+bi*wavelength**2/(wavelength**2-ci)
n=sp.sqrt(n2)
return n
n=indice_refraccion(wavelength, BK7_B,BK7_C)
dibujar_n(wavelength,n,’Sellmeier: BK7’)
n=indice_refraccion(wavelength, Fused_Silica_B,Fused_Silica_C)
dibujar_n(wavelength,n,’Sellmeier: Fused_Silica’)
n=indice_refraccion(wavelength, Zafiro_B,Zafiro_C)
dibujar_n(wavelength,n,’Sellmeier: Zafiro’)
"""
#tambien se puede obtener el valor para 1 dato solo, sin dibujar
wavelength=0.5
n05=indice_refraccion(wavelength, BK7_B,BK7_C)
print "el indice de refraccion BK7 para %4.4f um es %4.6f" %(wavelength,n05)
"""
plt.show()
1.530
1.525
1.520
n
1.515
1.510
1.470
1.465
n
1.460
1.455
1.770
1.765
n
1.760
1.755
1.750
material.
Otra de las expresiones típicas para ajustar el indice de refracción con la longitud de onda es la ecuación de
Cauchy,
𝐵 𝐶
𝑛(𝜆) = 𝐴 + + 4 + ..
𝜆2 𝜆
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Ejemplos de difraccion en campo lejano
#-------------------------------------
import scipy as sp
import matplotlib.pyplot as plt
m = 1; kg = 1; sg = 1; V = 1; C = 1;
Hz = 1 / sg; nm = 1e-9 * m; um=1e-6
q=1.6021765e-19*C
m=9.109382911e-31*kg
epsilon0=8.8541878176e-12
wavelength=sp.linspace(400*nm, 1000*nm,1000)
Fused_silica=(1.4580,0.00354*um**2)
Borosilicate_BK7=(1.5046,0.00420*um**2)
Hard_crown_K5=(1.5220,0.00459*um**2)
Barium_crown_BaK4 =(1.5690,0.00531*um**2)
Barium_BaF10=(1.6700,0.00743*um**2)
Dense_flint_SF10=(1.7280,0.01342*um**2)
#ejemplo de 1 resonancia
n=indice_refraccion(wavelength, Fused_silica)
dibujar_n(wavelength,n, titulo=’Cauchy: Fused_silica’)
n=indice_refraccion(wavelength, Borosilicate_BK7)
dibujar_n(wavelength,n, titulo=’Cauchy: Borosilicate_BK7’)
"""
wavelength=0.5
n05=indice_refraccion(wavelength,Borosilicate_BK7)
print "el indice de refraccion BK7 para %4.4f um es %4.6f" %(wavelength,n05)
"""
plt.show()
1.480
1.475
n
1.470
1.465
1.530
1.525
1.520
n
1.515
1.510
En los medios conductores tenemos cargas ligadas (electrones internos) y cargas libres. Sin embargo, éstos últimos
son los que más afectan al índice de refracción. Por ello, suponemos que sólo hay cargas libres. La ecuación de
índice de refracción se simplifica a
𝑞2 1
𝑛2𝑐 = 1 − 𝑁𝑉′ .
𝑚𝜖0 𝜔 (𝜔 + 𝑖𝛾)
𝜔𝑝2
𝑛2𝑐 (𝜔) ≈ 1 − .
𝜔2
donde
√︃
𝑞2
𝜔𝑝 = 𝑁𝑉′
𝑚𝜖0
import scipy as sp
import matplotlib.pyplot as plt
m = 1; kg = 1; sg = 1; V = 1; C = 1;
Hz = 1 / sg; nm = 1e-9 * m; um=1e-6
q=1.6021765e-19*C; m=9.109382911e-31*kg
epsilon0=8.8541878176e-12; c=299792458*m/sg
if wp>0:
plt.subplot(211)
plt.axvline(x=wp, ymin=0, ymax=1, lw=2, color=’k’, linestyle=’--’)
plt.subplot(212)
plt.axvline(x=wp, ymin=0, ymax=1, lw=2, color=’k’, linestyle=’--’)
plt.tight_layout()
def ejemplo(Nv,gamma):
wp=sp.sqrt(Nv*q**2/(m*epsilon0))
""" print "la frecuencia de resonancia es %4.6g" %(wp)
print "la longitud de onda de resonancia es %4.6g" %(2*sp.pi*c/wp)"""
w=sp.linspace(wp/4,wp*4,1000)
nc=indice_refraccion(w, Nv,gamma)
dibujar_n(w,nc,wp)
ejemplo(Nv=1e25, gamma=1e12)
ejemplo(Nv=1e25, gamma=1e14)
plt.show()
1.0
0.8
0.6
n
0.4
0.2
0.0 1 2 3 4 5 6 7
ω (Hz) 1e14
4.0
3.5
3.0
2.5
2.0
κ
1.5
1.0
0.5
0.0 1 2 3 4 5 6 7
ω (Hz) 1e14
1.6
1.4
1.2
1.0
n
0.8
0.6
0.4 1 2 3 4 5 6 7
ω (Hz) 1e14
2.0
1.5
1.0
κ
0.5
0.0 1 2 3 4 5 6 7
ω (Hz) 1e14
Ecuaciones de Fresnel
117
PIMCD2013-python, Publicación 1.0
Descarga de archivos:
fresnel.py. Archivo con las funciones principales fresnel.py
fresnel_aire_vidrio.py. Archivo con el ejemplo del del calculo de la amplitud, fase e intensidad
de las componentes paralela y perpendicular en una intercara aire-vidrio fresnel_aire_vidrio.py
fresnel_vidrio_aire.py. Archivo con el ejemplo del del calculo de la amplitud, fase e intensidad
de las componentes paralela y perpendicular en una intercara aire-vidrio fresnel_vidrio_aire.py
fresnel_aire_metal.py. Archivo con el ejemplo del del calculo de la amplitud, fase e intensidad de
las componentes paralela y perpendicular en una intercara aire-vidrio fresnel_aire_metal.py
9.1 Introducción
Las ecuaciones de Fresnel permiten calcular los campos transmitido y reflejado cuando una onda armónica plana
incide sobre una interfaz entre dos medios con índice de refracción distinto. Tienen una dependencia del estado
de polarización del haz incidente (paralelo al plano de incidencia, perpendicular al plano de incidencia o una
combinación), de los índices de refracción del medio de entrada y de salida (𝑛1 , 𝑛2 ) y del ángulo de incidencia 𝜃𝑖 .
El ángulo del haz transmitido 𝜃𝑡 se obtiene a través de la ley de Snell: 𝑛1 𝑠𝑖𝑛(𝜃𝑖 ) = 𝑛2 𝑠𝑖𝑛(𝜃𝑡 ).
Dichos campos reflejados y transmitido son lineales con el campo incidente y los coeficientes de proporcionalidad
son los coeficientes de fresnel. Para el caso de los campos reflejados
𝐸𝑟‖ = 𝑟‖ * 𝐸𝑖 ,
𝐸𝑟⊥ = 𝑟⊥ * 𝐸𝑖 ,
𝐸𝑡⊥ = 𝑡⊥ * 𝐸𝑖 .
Un análisis riguroso de cómo calcular dichos coeficientes de Fresnel a partir de las ecuaciones de Maxwell se
pueden encontrar en el libro de Cabrera 1 (capítulos 9 y 10). Para el caso de que los dos medios sean dieléctricos
estas ecuaciones resultan ser
𝑛2 𝑐𝑜𝑠(𝜃𝑖 ) − 𝑛1 𝑐𝑜𝑠(𝜃𝑡 )
𝑟‖ = ,
𝑛2 𝑐𝑜𝑠(𝜃𝑖 ) + 𝑛1 𝑐𝑜𝑠(𝜃𝑡 )
𝑛1 𝑐𝑜𝑠(𝜃𝑖 ) − 𝑛2 𝑐𝑜𝑠(𝜃𝑡 )
𝑟⊥ = ,
𝑛1 𝑐𝑜𝑠(𝜃𝑖 ) + 𝑛2 𝑐𝑜𝑠(𝜃𝑡 )
2 𝑛1 𝑐𝑜𝑠(𝜃𝑖 )
𝑡‖ = ,
𝑛2 𝑐𝑜𝑠(𝜃𝑖 ) + 𝑛1 𝑐𝑜𝑠(𝜃𝑡 )
2 𝑛1 𝑐𝑜𝑠(𝜃𝑖 )
𝑡⊥ = .
𝑛1 𝑐𝑜𝑠(𝜃𝑖 ) + 𝑛2 𝑐𝑜𝑠(𝜃𝑡 )
Para el caso de que uno de los medios tenga índice de refracción comlejo, es decir sea un medio absorbente,
entonces las ecuaciones son algo más complicadas. Se pueden encontrar en el libro de Cabrera 1 , capítulo 9.
1 J.M. Cabrera, F.J. López, F. Agulló López ; Óptica Electromagnética: Fundamentos; Addison-Wesley (2001) ISBN 9788478290215
En el archivo fresnel.py se han implementado las funciones que permiten el cálculo de los coeficientes y las
reflectancias. Dichas funciones tienen la entrada de los índices de refracción n1, n2 y de un array de números
que representan el ángulo de incidencia. La salida de estas funciones son: r_perp, r_par, t_perp, t_par cuando se
calculan coeficientes y R_perp, R_par, T_perp, T_par cuando se calculan reflectancias o transmitancias. Estos
parámetros los aceptan las funciones para dibujar.
La variable theta_i puede ser un número o un array generado con numpy. De esta forma se simplifican las repre-
sentaciones y se acelera enormemente el tiempo de cálculo. Por consiguiente, las funciones de cálculo devuelven
un array para los coeficientes 𝑟‖ , 𝑟⊥ , 𝑡‖ y 𝑡⊥ cuyo tamaño es igual al 𝜃𝑖 .
#Coeficientes de Fresnell
r_par=(n2*cos_i-n1*cos_t)/(n2*cos_i+n1*cos_t)
r_perp=(n1*cos_i-n2*cos_t)/(n1*cos_i+n2*cos_t)
t_par=(2*n1*cos_i)/(n2*cos_i+n1*cos_t)
t_perp=(2*n1*cos_i)/(n1*cos_i+n2*cos_t)
Definidos los coeficientes de Fresnel se puede calcular la transmitancia y la reflectancia entendiendo estas magni-
tudes como la relación entre la intensidad reflejada o transmitida y la intensidad del haz incidente(más detalles en
1
). Así estos parámetros pueden ser derivados de los coeficientes de Fresnell como se muestra en el código a partir
de la función reflectancia_transmitancia_dielectricos.
def reflectancia_transmitancia_dielectricos(n1, theta_i, n2):
""""Cabrera. pag 266"""
#Se calculan lo coeficientes de Fresnel
r_perp, r_par, t_perp, t_par= coeficientesFresnel_dielectricos(n1, theta_i, n2)
Los medios absorbentes se caracterizan por tener un índice de refracción complejo. Nuevamente las relaciones de
Fresnel permiten determinar los coeficientes de transmisión y reflexión.
def coeficientesFresnel_complejo(n1, theta_i, n2c):
"""
Calcula las componentes rs y rp mediante las eq. de Fresnel
n^=n-ik
ejemplo:
theta_i=sp.linspace(0*grados,90*grados,10),
#Reflectancia
R_perp=abs(r_perp)**2
R_par=abs(r_par)**2
#Precalculo
kiz=cos(theta_i)
ki2 = n1**2
ktcz=sp.sqrt(n2c**2-n1**2*sin(theta_i)**2)
ktc2=n2c**2
n2R=sp.real(n2c)
kappa2=sp.imag(n2c)
B=n2R**2-kappa2**2-n1**2*sin(theta_i)**2
ktz=sp.sqrt(0.5*(B+sp.sqrt(B**2+4*n2R**2*kappa2**2)))
#Transmitancias
T_perp=ktz*abs(t_perp)**2/kiz
T_par=ki2*sp.real(ktcz/ktc2)*abs(t_par)**2/kiz
Hasta el moemnto se han implementado fisntintas funciones que permiten determinar los coeficientes de Fres-
nel en función del ángulo del haz incidente y de los índices de refracción del medio y del material. En es-
te epígrafe, se pretende definir una función de debujo capaz de dibujar los coeficientes de Fresnel en fun-
ción de 𝜃𝑖 . Los datos que dibuja son los resultantes de coeficientesFresnel_dielectricos() o de
coeficientesFresnel_complejo().
def dibujarCoeficientes(n1, theta_i, n2, r_perp, r_par, t_perp, t_par, nombreArchivo = ’’):
"""
Dibuja las ecuaciones de fresnel en funcion del angulo de entrada
"""
#Generacion della figura
plt.figure(figsize=(12,6))
#Amplitud
plt.subplot(1,2, 1)
plt.plot(theta_i / grados, abs(r_perp)*sp.sign(r_perp) , ’k--’, lw = 2,\
label = u"$r_{\perp}$")
plt.plot(theta_i / grados, abs(r_par)*sp.sign(r_par) , ’k’, lw = 2,\
label = u"$r_{\parallel}$")
plt.plot(theta_i / grados, abs(t_perp)*sp.sign(t_perp) , ’r--’, lw = 2,\
label = r"$t_{\perp}$")
plt.plot(theta_i / grados, abs(t_par)*sp.sign(t_par) , ’r’, lw = 2,\
label = r"$t_{\parallel}$")
#Leyenda de los ejes
plt.xlabel(r"$\phi (grados)$", fontsize = 22)
plt.ylabel(r"$Amplitud$", fontsize = 22)
plt.ylim(-1., 2.01)
plt.legend(loc=2,prop={’size’:18})
#Fase
plt.subplot(1,2, 2)
plt.plot(theta_i / grados, sp.unwrap(sp.angle(r_perp),2*sp.pi), ’k--’, lw = 2,\
label = r"$r_{\perp}$")
plt.plot(theta_i / grados, sp.unwrap(sp.angle(r_par),2*sp.pi), ’k’, lw = 2,\
label = r"$r_{\parallel}$")
plt.plot(theta_i / grados, sp.unwrap(sp.angle(t_perp),2*sp.pi), ’r--’, lw = 2,\
label = r"$t_{\perp}$")
plt.plot(theta_i / grados, sp.unwrap(sp.angle(t_par),2*sp.pi), ’r’, lw = 2,\
label = r"$t_{\parallel}$")
#Leyenda de los ejes
plt.xlabel(r"$\phi (grados)$", fontsize = 22)
9.3 Ejemplos
def coeficientes():
"interfaz aire-vidrio"
#Coeficientes de Fresnel para un caso particular
r_perp, r_par, t_perp, t_par = coeficientesFresnel_dielectricos(n1 = 1.5,\
theta_i = 45 * grados, n2 = 1.0)
#Muestra de resultados
print "coeficientes de reflexion paralelo: r_par= (%2.4f,%2.4f)" % (r_par.real,\
r_par.imag)
print "coeficientes de reflexion perpendicular: r_perp= (%2.4f,%2.4f)" % (r_perp.real,\
r_perp.imag)
print "coeficientes de transmision paralelo: t_par= (%2.4f,%2.4f)" % (t_par.real,\
t_par.imag)
print "coeficientes de transmision perpendicular: t_perp= (%2.4f,%2.4f)" % (t_perp.real,\
t_perp.imag)
if __name__ == ’__main__’:
coeficientes()
Se supone la propagación de un haz en aire, que incide a 45 grados sobre un vidrio dieléctrico con índice 1.5. Así,
el resultado es:
>> coeficientes de reflexion paralelo: r_par= (0.2800,-0.9600)
>> coeficientes de reflexion perpendicular: r_perp= (0.8000,-0.6000)
>> coeficientes de transmision paralelo: t_par= (1.9200,-1.4400)
>> coeficientes de transmision perpendicular: t_perp= (1.8000,-0.6000)
En este ejemplo se considera la incidencia de un haz de luz en el aire sobre una superficie de vidrio con índice
1.5. Se representan la amplitud transmitida y reflejada, así como la fase reflejada en función del ángulo de inci-
dencia. Como se puede ver en las curvas de amplitud, la transmitancia paralela y perpendicular son relativamente
similares. Si se representan las transmitancias y reflectancias, se observa que cuando la reflectancia es mínima la
transmitancia es máxima. Relación lógica puesto que R+T=1. No obstante es de especial interés este extremal,
llamandose ángulo de Brewster a aquel para el que la reflectancia es mínima. Así se observa que la fase cambia
radicalmente en dicho punto, y la luz reflejada resulta perfectamente polarizada.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#----------------------------------------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#----------------------------------------------------------------------
"""
ejemplos para las ecuaciones de Fresnel
"""
#Carga de paquetes
import sys
sys.path.append(’../’)
from fresnel import * #@UnusedWildImport
def fresnel_aire_vidrio():
"interfaz aire-vidrio"
n1 = 1.
n2 = 1.5
theta_i = sp.linspace(0 * grados, 90 * grados, 500)
#Transmitancia y reflectancia
R_perp, R_par, T_perp, T_par= reflectancia_transmitancia_dielectricos(n1, theta_i, n2)
dibujarTransmitancias(n1, theta_i, n2, R_perp, R_par, T_perp, T_par)
if __name__ == ’__main__’:
fresnel_aire_vidrio()
plt.show()
2.0 3.5
r r
1.5 r 3.0 r
t t
2.5
1.0 t t
Amplitud
2.0
fase
0.5
1.5
0.0
1.0
0.5 0.5
1.00 10 20 30 40 50 60 70 80 90 0.00 10 20 30 40 50 60 70 80 90
φ(grados) φ(grados)
1.0
0.8
0.6
I
0.4
R
R
0.2
T
T
0.0
0 10 20 30 40 50 60 70 80 90
φ(grados)
Este ejemplo es el contrario al anterior, se supone un haz de luz propagandose en vidrio, y se analiza el comporta-
miento del haz a la salida del material. Nuevamente se observa la reflexión de Brewster en un ángulo cercano a los
40 grados, y como cerca de este la transmitancia cambia abruptamente hasta el punto que únicamente se produce
reflexión del haz.
def fresnel_vidrio_aire():
"interfaz vidrio-aire"
n1 = 1.5
n2 = 1.
theta_i = sp.linspace(0 * grados, 90 * grados, 500)
2.0 4
r r
3
1.5 r r
t 2 t
1.0 t t
1
Amplitud
fase
0.5 0
1
0.0
2
0.5
3
1.00 10 20 30 40 50 60 70 80 90 40 10 20 30 40 50 60 70 80 90
φ(grados) φ(grados)
1.0
0.8
0.6
I
0.4
R
R
0.2
T
T
0.0
0 10 20 30 40 50 60 70 80 90
φ(grados)
9.3.4 Interfaz aire-metal
Los metales se caracterizan por tener un índice de refracción complejo siendo negativa la parte imaginaria del
mismo. Así se supone la incidence de un haz sobre una intercara aire-metal, con índice de refracción 𝑛 = 0,769 −
𝑖6,08. Es destacable como la variación en la fase de la componente paralela, es prácticamente invariante bajo
cambios en el ángulo de incidencia.
def fresnel_aire_metal():
"interfaz aire-metal"
n1 = 1.
#el metal se introduce con numero complejo cuya parte imaginaria es negativa
n2c = 0.769 - 6.08j
theta_i = sp.linspace(0 * grados, 90 * grados, 500)
2.0 4
r r
3
1.5 r r
t 2 t
1.0 t t
1
Amplitud
fase
0.5 0
1
0.0
2
0.5
3
1.00 10 20 30 40 50 60 70 80 90 40 10 20 30 40 50 60 70 80 90
φ(grados) φ(grados)
1.0
0.8
0.6
I
0.4
R
R
0.2
T
T
0.0
0 10 20 30 40 50 60 70 80 90
φ(grados)
9.4 Ejercicios
9.5 Referencias
Pulsos de luz
129
PIMCD2013-python, Publicación 1.0
10.1 Introducción
Un pulso temporal ‘plano’ se puede tratar como una suma infinita de ondas armónicas planas, pues forman una
base. Para calcular los componentes de cada una de las ondas armónicas de frecuencia 𝜔 que componen el pulso
se tiene que realizar una transformación de Fourier temporal,
∫︁ ∞
E (𝑧, 𝑡) = E0,𝜔 𝑒𝑖(𝑘𝜔 𝑧−𝜔𝑡) 𝑑𝜔,
−∞
donde E0,𝜔 es la amplitud compleja de cada una de las ondas planas. Para calcular esta amplitud se tiene que
recurir a la transformación de Fourier inversa
∫︁ ∞
1
E0,𝜔 = √ E (𝑧 = 0, 𝑡) 𝑒𝑖𝜔𝑡 𝑑𝑡,
2𝜋 −∞
Existen dos casos particularmente útiles como son el pulso gaussiano y el pulso de exponencial, que discutiremos
brevemente a continuación.
(𝜔 − 𝜔0 )2
[︂ ]︂
E0,𝜔 = E0 𝑒𝑥𝑝 − .
2∆𝜔 2
#Modulos empleados
import scipy as sp
import matplotlib.pyplot as plt
#Unidades
sg = 1
Hz = 1 / sg #hertzio
t= sp.linspace(-3/gamma,3/gamma,n_puntos)
1.0 1.0
0.8
0.5
perfil Gaussiano
0.6
E(t)
0.0
0.4
0.5
0.2
Otro pulso que se utiliza frecuentemente es el pulso exponencial, pues es el modelo clásico de cómo los átomos
emiten luz. Una vez excitados pierden energía hasta que vuelven al estado estacionario. El modelo de Lorentz
asume que el desplazamiento resulta
Al realizar la transformada de Fourier resulta que la distribución de frecuencias es compleja (se producen desfases).
𝐸𝑜 ux
E𝜔 =
𝛾/2 − 𝑖(𝜔 − 𝜔𝑜 )
En este caso, la densidad de energía espectral, esto es, la cantidad de energía electromagnética por unidad espectral
que llega por unidad de superficie
𝛾 2 /4 1
𝐽(𝜔) = 𝜀𝑜 𝑐|𝐸𝜔 |2 = 𝐽(𝜔𝑜 ) , = 𝐽(𝜔𝑜 ) ,
𝛾 2 /4 + (𝜔 − 𝜔𝑜 )2 1 + 4(𝜔 − 𝜔𝑜 )2 /𝛾 2
En el siguiente código se muestra cómo generar una gráfica con el espectro gaussiano y el pulso generado.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""pulso de gauss"""
#Definicion de frecuencia
sg= 1
Hz = 1 / sg #hertzio
#campo
campo=sp.exp(-gamma*t)*sp.cos(w0*t)
#Figuras
plt.figure(figsize=(10,5))
plt.subplot(1,2,1)
plt.plot(t, campo, ’k’, linewidth = 2)
plt.xlabel(’$t \; (s)$’, fontsize = 18)
plt.ylabel(’$E(t)$’, fontsize = 18)
1.0 1.0
0.8
0.5
perfil Lorenziano
0.6
E(t)
0.0
0.4
0.5
0.2
1.00.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 0.02.0 2.5 3.0 3.5 4.0
t (s) 1e 14 ω (Hz) 1e15
Desde el punto de vista teórico también es importante un pulso cuadrado, que se obtiene como una función coseno
dentro del intervalo y 0 fuera.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""pulso de gauss"""
#Modulos empleados
import scipy as sp
import matplotlib.pyplot as plt
#Unidades
sg = 1
Hz = 1 / sg #hertzio
t= sp.linspace(-3/gamma,3/gamma,n_puntos)
1.0 1.0
0.8
0.5
0.6
perfil sinc
0.4
E(t)
0.0
0.2
0.0
0.5
0.2
Interferencias
137
PIMCD2013-python, Publicación 1.0
11.1 Introducción
El efecto de las interferencias surge directamente de que las ecuaciones de Maxwell son lineales en los campos
eléctricos y magnéticos. Supongamos que E1 y E2 son soluciones. Entonces
E = E1 + E2
también es una solución. Sin embargo, el campo no es un parámetro observable, sino que lo es el promedio
temporal del vector de Poynting, que se define como
1
S≡ E ∧ B,
𝜇0
con tiene unidades de [S] = 𝑊/𝑚2 . Al particularizar al caso de ondas armónicas planas tenemos
√︂
1 𝜀0 2
⟨S⟩ = |E| u𝑘 ,
2 𝜇0
Si no existiera el tercer término, entonces ocurre que 𝐼 = 𝐼1 + 𝐼2 , que es lo que normalmente vemos en la
naturaleza.
Para que se cumplan las interferencias, pues necesitamos que la luz esté polarizada, sea bastante monocromática
y sea coherente. En particular, se deben verificar:
Únicamente interfieren ondas con la misma frecuencia, 𝜔1 = 𝜔2
Las ondas que interfieren deben tener estados de polarización no ortogonales. E01 · E02 ̸= 0
Coherencia: Las fases en el término interferencial deben tener poca dependencia de (𝑡, 𝑟)
En este capítulo asumiremos las mejores condiciones para producir las interferencias. Las 2 ondas tienen la misma
polarización, son puramente monocromáticas y coherentes. De esta forma, desaparece el carácter vectorial de las
interferencias.
11.2 Programación
Debido a la sencillez conceptual de las interferencias, las utilizaremos para analizar los distintos tipos de pro-
gramación que se puede realizar con Python, como es la programación con scripts (todo el código en un mismo
archivo lineal), la programación funcional (se escriben funciones que simplifican lo cálculos) y la programación
con clases (donde funciones y datos están íntimamente relacionados). Finalmente veremos un procedimiento sen-
cillo para desarrollar vídeos, simulando un interferómetro de Michelson.
para poder representar esta ecuación en muchas posiciones x,y entonces X e Y son matrices generadas como
tamano=100*um
x=sp.linspace(-tamano,tamano, 256)
y=sp.linspace(-tamano,tamano, 256)
X,Y=sp.meshgrid(x,y)
El método lineal es quizás el más sencillo para iniciarse, pues la programación está en el mismo archivo y el
código se ejecuta de arriba hacia abajo en el programa. Un ejemplo de esta programación está en la función
interferencias_ondasPlanas1.py. El archivo consiste en la definición de dos ondas de la misma longitud de onda y
la posterior superposición de las mismas, para finalmente obtener la intensidad. Así se obtiene el patrón de franjas
de la figura.
#!/usr/local/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""
Interferencia entre ondas planas.
Programacion lineal.
"""
#variables globales
um=1
grados=sp.pi/180
#Tamano
tamano=100*um
x=sp.linspace(-tamano,tamano, 256)
y=sp.linspace(-tamano,tamano, 256)
X,Y=sp.meshgrid(x,y)
A2=1
theta2=90*grados
phi2=1*grados
#proceso interferencial
u=u1+u2
#calculo de la intensidad
I=abs(u)**2
#grafica de la intensidad
plt.figure() #creacion de la figura
#escalado de la grafica
plt.axis(’scaled’)
#etiquetas
plt.xlabel("$x (\mu m)$", fontsize = 22)
plt.ylabel("$y (\mu m)$", fontsize = 22)
plt.title("$Intensidad(x,y)$", fontsize = 22)
#color
h1.set_cmap("gist_heat")
#muestra la grafica
plt.show()
100
Intensidad(x,y) 4.0
3.6
3.2
50
2.8
2.4
y(µm)
0 2.0
1.6
1.2
50
0.8
0.4
100100 50 0 50 100 0.0
x(µm)
11.3.2 Método funcional
Si nos damos cuenta en el código anterior, la onda plana se generar dos veces, una para 𝐸1 , y otra para 𝐸2 . Esto
se puede evitar definiendo una función que denominamos onda_plana() a la cual llamamos dos veces. Los
parámetros de entrada de la función son aquellos que alguna vez podemos variar. También podemos generar una
función que realiza la gráfica. De esta forma la podemos llamar también varias veces.
#!/usr/local/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""
Interferencia entre ondas planas.
Programacion con funciones.
"""
#variables globales
um=1
grados=sp.pi/180
#parametros opticos
wavelength=0.6328*um
k=2*sp.pi/wavelength
#Tamano
tamano=100*um
x=sp.linspace(-tamano,tamano, 256)
y=sp.linspace(-tamano,tamano, 256)
X,Y=sp.meshgrid(x,y)
#definicion de la grafica
def dibujar_grafica(I):
#grafica de la intensidad
plt.figure() #creacion de la figura
#datos para ajustar el dibujo a la escala
extension = [x.min(), x.max(), y.min(), y.max()]
#dibujar intensidad + acondicionamiento de la grafica
h1 = plt.imshow(I, interpolation = ’bilinear’, aspect = ’auto’,
origin = ’lower’, extent = extension)
#escalado de la grafica
plt.axis(’scaled’)
#etiquetas
plt.xlabel("$x (\mu m)$", fontsize = 22)
plt.ylabel("$y (\mu m)$", fontsize = 22)
plt.title("$Intensidad(x,y)$", fontsize = 22)
#color
h1.set_cmap("gist_heat") #RdBu
#dibujar barra de colores
plt.colorbar()
100
Intensidad(x,y) 4.0
3.6
3.2
50
2.8
2.4
y(µm)
0 2.0
1.6
1.2
50
0.8
0.4
100100 50 0 50 100 0.0
x(µm)
100
Intensidad(x,y) 4.0
3.6
3.2
50
2.8
2.4
y(µm)
0 2.0
1.6
1.2
50
0.8
0.4
100100 50 0 50 100 0.0
x(µm)
Como se ve, el código incluido en las funciones se puede reutilizar. No obstante, las variables que utiliza la funcion
onda_plana() tienen dos orígenes. Uno como variables globales definidas anteriormente (como puede ser k,
X, Y y otras variables que se introducen a través de la definción de la función A, 𝜃, 𝜑.
En las clases, las funciones y los datos están todos bajo un mismo marco, por lo que no existe el peligro de
modificar las variables globales de forma inadvertida. Normalmente la clase se define en una función (compleja)
y las aplicaciones de la clase llaman a esta función. Esta forma permite generar las figuras mostradas debajo del
código
#!/usr/local/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""
Metodo que utiliza las clases
"""
u1 = fuenteXY()
u2 = fuenteXY()
plt.show()
4.0
200 3.6
3.2
100 2.8
2.4
y(µm)
0 2.0
1.6
100 1.2
0.8
200 0.4
100 100
y(µm)
y(µm)
0 0
100 100
200 200
200 100 0 100 200 200 100 0 100 200
x(µm) x(µm)
0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6 1.8 2.0 0.0 0.4 0.8 1.2 1.6 2.0 2.4 2.8
Dentro de la clase fuentesXY (no es necesario comprender todas las funciones todavía), las funciones que
hemos utilizado son:
__init__()
onda_plana()
def __init__(self, x = x0, y = y0, wavelength = lambda0):
"""Se inicia un nuevo experimento"""
#arrays de entrada
self.x = x
self.y = y
#Longitud de onda
self.wavelength = wavelength
#Generacion de un mallado
self.X, self.Y = sp.meshgrid(x, y)
#Predefinicion del campo
self.u = sp.zeros(sp.shape(self.X), dtype = complex)
Veamos ahora el proceso de interferencia de 2 haces de Gauss. Estos hace se pueden definir como
𝜌2 𝜌2
(︂ )︂ (︂ )︂
𝜔0
𝐸(𝑥, 𝑦, 𝑧) = 𝐴0 exp − 2 exp 𝑖𝑘𝑧 + 𝑖𝑘 − 𝑖𝜁(𝑧)
𝜔(𝑧) 𝜔 (𝑧) 2𝑅(𝑧)
donde
𝑧0 𝜆
𝜔02 =
𝜋
√︃ (︂ )︂2
𝑧
𝜔(𝑧) = 𝜔0 1+
𝑧0
[︂ ]︂
𝑧 2
𝑅(𝑧) = 𝑧 1 + ( )
𝑧0
Este tipo de haces se puede programar fácilmente. Asumamos que estamos en el foco del haz z=0 y los haces están
rotados. Entonces también se van a producir interferencias entre los haces.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""
ejemplos de interferencias
"""
#variables globales
um=1
grados=sp.pi/180
wavelength=0.6328*um
tamano=100*um
x=sp.linspace(-tamano,tamano, 256)
y=sp.linspace(-tamano,tamano, 256)
X,Y=sp.meshgrid(x,y)
if __name__ == ’__main__’:
interferenciasGauss(theta=20*grados, titulo=r’$\theta=20^{0}$’)
interferenciasGauss(theta=40*grados, titulo=r’$\theta=40^{0}$’)
plt.show()
0
θ =200
3.6
50 3.2
2.8
100 2.4
2.0
150 1.6
1.2
200 0.8
0.4
250 0.0
0 50 100 150 200 250
0
θ =400
3.6
50 3.2
2.8
100 2.4
2.0
150 1.6
1.2
200 0.8
0.4
250 0.0
0 50 100 150 200 250
Aquí está todo programado en funciones, tanto la amplitud del haz de Gauss en z=0, como el procedimiento de
dibujar y la función de ejecución __main__. De esta forma se puede reutilizar el código de una forma muy
eficiente, además de entenderse mejor el código. Los parámetros entre las funciones se pasan a través de las
variables de entrada.
Finalmente, vemos cómo se puede representar esto a partir de la clase FuenteXY. La forma de programar las
interferencias es mucho más sencilla y clara, casi pseudocódigo
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""
ejemplos de interferencias
"""
#Carga de paquetes y librerias
import scipy as sp
import matplotlib.pyplot as plt
import sys
sys.path.append(’../’)
from fuentesXY import fuenteXY, grados, um
#definicion de parametros
x=sp.linspace(-100*um,100*um,256)
y=sp.linspace(-100*um,100*um,256)
wavelength=0.6328*um
#onda 1
u1 = fuenteXY(x,y,wavelength)
u1.haz_gauss(A=1, r0=(0*um,0*um), w=(50*um, 50*um), theta=20.*grados, phi=2*grados)
#onda 2
u2 = fuenteXY(x,y,wavelength)
u2.haz_gauss(A=1, r0=(0*um,0*um), w=(50*um, 50*um), theta=-20.*grados, phi=2*grados)
#Superposicion
u12 = u1 + u2
#Representacion
u12.dibujar(tipo=’intensidad’)
plt.show()
100
3.6
3.2
50
2.8
2.4
y(µm)
0 2.0
1.6
1.2
50
0.8
0.4
100100 50 0 50 100 0.0
x(µm)
Como ejemplo de programación con clases veremos las interferencias entre ondas esféricas. Estas son de gran
importancia teórica pues nos permiten analizar los interferómetros de Young y de Michelson. La diferencia fun-
damental estriba en la posición de las fuentes. Mientras que en el interferómetro de Young las dos fuentes están
desplazadas en el eje x, en el interferometro de Michelson están desplzadas en el eje z.
Como veremos, la programación con clases nos permite centrarnos en el problema, ya que la programación es de
alto nivel (el profesor ya desarrolla las funciones de generación de las fuentes).
Como hemos dicho las dos fuentes están desplazadas en el eje x. Esto genera una distribución de intensidad en
forma de elipses que, si nos centramos en el eje, son muy próximas a franjas verticales, como puede ver en la
siguiente función.
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Interferencias entre 2 ondas esfericas desplazadas en el eje x
#-------------------------------------
import sys
sys.path.append(’../’)
from fuentesXY import fuenteXY, grados, um
plt.show()
200 3.6
3.2
100 2.8
2.4
y(µm)
0 2.0
1.6
100 1.2
0.8
200 0.4
Ahora las fuentes están desplazadas en el eje z. Esto genera una distribución de intensidad en forma de anillos,
como se puede ver en la siguiente función.
#!/usr/local/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""
Interferencias
"""
#Carga de paquetes y librerias
import scipy as sp
import matplotlib.pyplot as plt
import sys
sys.path.append(’../’)
from fuentesXY import fuenteXY, grados, um
plt.show()
0.0000027
200
0.0000024
0.0000021
100
0.0000018
0.0000015
y(µm)
0
0.0000012
100 0.0000009
0.0000006
200 0.0000003
Cuando tenemos un desplazamiento combinado x-z entre las dos fuentes, entonces la situación no es tan sencilla,
apareciendo unas elipses que nos son familiares si hemos ajustado un interferómetro de Michelson.
#!/usr/local/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
#Representacion
u12.dibujar(tipo=’intensidad’)
plt.show()
0.0000036
200 0.0000032
0.0000028
100
0.0000024
0.0000020
y(µm)
0
0.0000016
0.0000012
100
0.0000008
200 0.0000004
Para finalizar mostramos un código que ejecuta un vídeo de qué ocurre en un interferómetro de Michelson cuando
uno de los espejos se desplaza.
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: grafica animada simulando un interferometro de Michelson
#-------------------------------------
import time
import gobject
import gtk #@UnusedImport
import scipy as sp
import matplotlib
matplotlib.use(’GTKAgg’)
import matplotlib.pyplot as plt
import matplotlib.cm as cm
um=1;
mm=1000*um;
fig = plt.figure(1)
a = plt.subplot(111)
wavelength=0.6328*um
k=2*sp.pi/wavelength
X,Y=sp.meshgrid(x,y)
I=1+sp.cos(2*k*d*cos_theta)
im = a.imshow( I, cmap=cm.gist_heat)
manager = plt.get_current_fig_manager()
cnt = 0
tstart = time.time()
def updatefig(*args):
#parametros globales definidos fuera de la funcion
global x, y, cnt, start, k, cos_theta, d
d-=.025*um
I=1+sp.cos(2*k*d*cos_theta)
im.set_array(I)
manager.canvas.draw()
cnt += 1
if cnt==250:
FPS=cnt/(time.time() - tstart)
cnt = 0
gobject.idle_add(updatefig)
plt.show()
tmp=raw_input("pulse enter para cerrar:")
Módulos de Óptica
Una vez que conocemos los módulos científicos, en el Departamento de Óptica de la UCM hemos desarrollado
módulos específicos para aplicaciones ópticas. .
12.1.1 Introducción
La mayor parte de las veces observamos un campo óptico en un plano XY, como es en el caso de la detección
sobre una pantalla o sobre una cámara CCD o CMOS.
Aquí es donde vamos a definir los campos eléctricos de observación. En este módulo realizaremos un tratamiento
escalar, que permitirá realizar operaciones sobre el campo eléctrico. Asimismo, será utilizado por otros módulos
como mascarasXY.py y fuentesXY.py, puesto que las máscaras y las fuentes se pueden tratar de idéntica forma.
En este capítulo se pretende estudiar el funcionamiento del módulo, no desarrollar ejemplos y aplicaciones ópticas.
Se inicializa con la función __init__ que dice las operaciones cuando se carga.
157
PIMCD2013-python, Publicación 1.0
El campo self.u es nulo hasta que no se utilice una función que lo rellena.
Los dos campos que se suman o multiplican tienen que tener la misma dimensión. Un pequeño ejemplo de uso.
Utilizamos la clase fuenteXY, pero es lo mismo, pues una hereda las funciones de la otra.
u1 = fuenteXY()
u2 = fuenteXY()
def binarizar(self, tipo = "amplitud", corte = None, nivel0 = None, nivel1 = None, campoNuevo = Fa
def perfil(self, punto1=’’, punto2=’’, npixels=1000, tipo=’intensidad’, order=2):
Rayleigh-Sommerfeld:
def RS(self, z = 10 * mm, newField = True, xout = None, yout = None):
def progresion(self, zs=sp.linspace(1*mm,2*mm,500), tipo=’intensidad’, generarTemporales=True):
def testCamposXY():
"""poner un comentario # en aquellas funciones que no se quieren ejecutar"""
#test_FFT_ondaPlana()
#test_FFT_Gauss()
#test_FFTrectangulo()
#test_RS()
test_RS_tamano_distinto()
#test_RS_inversa()
#test_variosPasos()
Las ejemplos se basan en la función self.dibujar() que dibuja los datos que hay en los campos. Hay diversos tipos
de dibujos ‘intensidad’, ‘amplitud’, ‘fase’, campo’, ‘campoReal’.
def dibujar(self, tipo = ’intensidad’, logaritmico = False, normalizar = False,\
titulo = " ", nombreArchivo = ’’, dibujar = True, valorCorte = 1):
""" * tipo: ’intensidad’, ’amplitud’, ’fase’, campo’, ’campoReal’
* logartimico: True o False
* normalizar: ’maximo’, ’area’, ’intensidad’ """
Asimismo es muy sencillo realizar dibujos logaritmicos, simplemente incluyendo True o False en la variable.
Si la variable nombreArchivo no es nula, se guarda un dibujo.
Si la variable normalizar es True el valor máximo del dibujo es la unidad.
Si se incluye un valorCorte, entonces se satura el dibujo al valor determinado.
Otra función que también es interesante es la de dibujar_perfil, que permite sacar una gráfica 1D del camp entre 2
puntos.
def dibujar_perfil(self, punto1=’’, punto2=’’, npixels=1000, tipo=’intensidad’, normalizar=’’, ord
12.2.1 Introducción
Para poder realizar tratamientos numéricos con haces de luz, lo primero que tenemos que hacer es definirlos. La
clase fuentesXY hereda las propiedades de la clase campoXY. Añade fuentes que se observan en el plano XY.
Para ello hemos desarrollado una clase que contiene distintos tipos de fuentes tales como ondas planas, ondas
esféricas, haces de Gauss, haces de Laguerre, vórtices y Zernikes. Veremos aquí las definiciones y los ejemplos
de utilización.
La clase fuentesXY está constituida por un conjunto de funciones. Estas funciones describen distintos tipos de
onda o fuentes de luz en forma de: onda plana, esférica, haz gaussiano, haz de Laguerre, o aberraciones de Zernike.
En el archivo fuentesXY_ejemplos, se muestra la obtención de las figuras de cada apartado.
La clase FuentesXY hereda funciones de la clase camposXY, que más general. No es necesario conocer el funcio-
namiento de esta clase camposXY. Cuando haga falta se hará referencia a ella como, por ejemplo, al realizar las
gráficas.
A modo de ejemplo veamos como se define y utiliza una fuente:
#definicion de parametros iniciales
tamano = 250 * um
npixels = 256
x0=sp.linspace(-tamano / 2, tamano / 2, npixels)
y0=sp.linspace(-tamano / 2, tamano / 2, npixels)
#Generacion de la clase
campo = fuenteXY(x = x0 , y = y0 , wavelength = 0.6328 * um)
#carga de la onda plana
campo.onda_plana(phi = 2 * grados, theta = 5 * grados)
#dibujar y guardar (si se escribe nombreArchivo)
campo.dibujar(tipo = ’fase’)
Se define una onda plana como aquella que su frente de onda es plano y normal a la velocidad de fase. Son
uno de los tipos de ondas más habituales en física. Y están definidas en regiones suficientemente alejadas de
fuentes de carga. Su representación matemática se realiza mediante funciones armónicas, así sea una onda plana
propagándose en el eje z con amplitud A, frecuencia constante, 𝜔, y con vector de onda ⃗𝑘 = 2𝜋 𝜆 (sin(𝜃) sin 𝜑⃗
𝑥+
cos(𝜃) sin 𝜑⃗𝑦 ), la orientación de los frentes de onda queda definida por dicho vector. De este modo, una onda
plana se expresa como
⃗
𝐸 = 𝐴𝑒𝑖𝑘⃗𝑟 .
En rigor, estamos obviando la parte temporal, puesto que a nuestros fines carece de importancia.
En representación compleja una onda plana se expresa como
para poder representar esta ecuación en muchas posiciones x,y entonces X e Y son matrices generadas como
tamano=100*um
x=sp.linspace(-tamano,tamano, 256)
y=sp.linspace(-tamano,tamano, 256)
X,Y=sp.meshgrid(x,y)
#----------------------------------------------------------------------
# Name: fuentesXY.py
#Author: Luis Miguel Sanchez Brea
# Licence: GPL
#----------------------------------------------------------------------
"""
Clases campoXY y Fuentes2D con las fuentes utilizadas para propagacion
"""
import scipy as sp
import matplotlib.pyplot as plt
import sys
sys.path.append(’../’)
def test_onda_plana():
"""Generacion de una onda plana"""
test_onda_plana()
plt.show()
100 2.4
1.6
50
0.8
y(µm)
0 0.0
0.8
50
1.6
100 2.4
100 50 0 50 100
x(µm)
12.2.4 Onda esférica
De manera análoga a la definición de onda plana, una onda esférica es aquella cuyos frentes de onda son superficies
esféricas. Las ecuaciones de Maxwell postulan la existencia de ondas electromagnéticas mediante la ecuación de
ondas. La resolución en esféricas de esta ecuación da lugar a ondas esféricas como soluciones a las ecuaciones de
Maxwell. Así se puede probar como la magnitud del campo eléctrico viene definida mediante
𝐴 𝑖𝑘𝑟
𝐸= 𝑒 .
𝑟
Las ondas esféricas han sido implementadas dentro de la clase fuenteXY mediante la función onda_esferica,
como se muestra en el código a debajo. La onda encuentra su fuente de emisión centrada en el punto de coorde-
nadas x0, y0. Debido a que los frentes de onda son círculos concéntricos, se ha definido una máscara circular
con el fin de no cortar ningún frente de onda, representando así únicamente los frentes de onda en el interior de
la máscara circular. Esto se logra con el último if del código. Finalmente se muestran las representaciones de la
amplitud y la fase.
En la figura se muestra la máscara de amplitud a la izquierda y a la derecha la fase en el plano XY. Es destacable
el decaimiento de la misma de con la distancia.
amplitud fase
300 300
200 200
100 100
y(µm)
y(µm)
0 0
100 100
200 200
300 300
300 200 100 0 100 200 300 300 200 100 0 100 200 300
x(µm) x(µm)
amplitud fase
300 300
200 200
100 100
y(µm)
y(µm)
0 0
100 100
200 200
300 300
300 200 100 0 100 200 300 300 200 100 0 100 200 300
x(µm) x(µm)
Un haz gaussiano es aquel en el que cualquier plano normal a la dirección de propagación a distribución de
intensidades del campo eléctrico se ajusta a una gaussiana. Así sea una gaussiana de varianza w, centrada en el
punto (x0,y0). Así se define amplitud como
(𝑥−𝑥0 )2 (𝑦−𝑦0 )2
𝑖( 2
2𝑤𝑥
− 2
2𝑤𝑦
)
𝐴𝑚 = 𝐴𝑒
Mientras que la fase vale
𝐴𝑚 𝛼
Esta última ecuación desglosada en fase y amplitud es la que se implementa en la función haz_gauss
A continuación se muestra una representación del perfil de intensidades para un haz gaussiano.
1.0
400 0.9
0.8
200 0.7
0.6
y(µm)
0 0.5
0.4
200 0.3
0.2
400 0.1
Las estructuras tipo vórtice son aquellas en las que la fase se comporta linealmente con la variable angular (en
coordenadas cilíndricas), esto es 𝜑(𝑟, 𝜃, 𝑧) = 𝑚𝜃, siendo m el orden del vórtice.
Ahora veremos cómo se puede llamar a esta función
y(µm)
0 0
100 100
200 200
300 300
300 200 100 0 100 200 300 300 200 100 0 100 200 300
x(µm) x(µm)
0.00 0.15 0.30 0.45 0.60 0.75 0.90 2.4 1.6 0.8 0.0 0.8 1.6 2.4
Ahora N vórtices
Vortices
(1) (2) (3) (4) (5)
Similarmente a los haces gaussianos se definen los modos de Laguerre como aquellos haces en los que el perfil de
intensidades sigue una distribución de modo de Laguerre, es decir una distribución
2
𝑘𝑟
−𝑖 2𝑅(𝑧)
2
𝑟
− 𝑤(𝑧) 𝑝 𝑟 2
2 +𝑖(2𝑝+𝑙+1)𝜁(𝑧) −𝑖𝑙𝜃 (−1) ( 𝑤(𝑧)2 )
𝑙/2 2𝑟2
𝑈 (𝑟, 𝜃, 𝑧) = 𝑒 𝑒 𝑒 𝐿𝑙𝑝 ( )
𝑤(𝑧)2
La función haz_laguerre realiza exactamente este cometido, pero desglosa el producto en pequeños términos
t1,t2,t3,t4.
Así se pueden extraer la amplitud y la fase del haz y ser representadas análogamente a los casos anteriores.
y(µm)
0 0
100 100
200 200
300 300
300 200 100 0 100 200 300 300 200 100 0 100 200 300
x(µm) x(µm)
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 2.4 1.6 0.8 0.0 0.8 1.6 2.4
Finalmente mostrados distintos modos de Laguerre para diferentes pares (l,p). Al aumentar l aumenta el número
de anillos, así como la intensidad local se reduce si se incrementa p.
(0,0) (1,0)
amplitud (2,0) (3,0)
(0,0) (1,0)
fase (2,0) (3,0)
Los frentes de onda de un haz de luz pueden presentar perturbaciones, debidas a la propia propagación en el medio
o introducidas por elementos ópticos estas perturbaciones reciben el nombre de aberraciones del haz. La forma
más común de clasificar las aberraciones es en las llamadas aberraciones de Zernike, las cuales hacen uso de la
base de polinomio que les da nombre, polinomios de Zernike. Estos polinomios están definidos sobre el círculo
unidad, en términos de dos variables radial, 𝑟 y acimutal, 𝜃 junto con dos parámetros, (m,n) que representan el
orden del Zernike. Así se define si n es par
Donde
(𝑛−𝑚)/2
∑︁ (−1)𝑘 (𝑛 − 𝑘)!
𝑅𝑛𝑚 (𝜌) = 𝜌𝑛−2 𝑘
𝑘! ((𝑛 + 𝑚)/2 − 𝑘)! ((𝑛 − 𝑚)/2 − 𝑘)!
𝑘=0
Esto ha sido implementado en la clase fuentesXY mediante la función fZernike, con argumentos, n, m y radio
La gran utilidad de los polinomios de Zernike es que permiten expresar cualquier aberración del frente de ondas
𝑛
como una suma de términos 𝑍𝑚 . Así, si 𝑐𝑛𝑚 son los coeficientes del polinomio de Zernike, cualquier aberración
en la fase podrá escribirse como
∑︁
𝑛
𝜑= 𝑐𝑛𝑚 𝑍𝑚
𝑛𝑚
Por tanto, haciendo uso de los polinomios definidos con fZernike se determina la aberración del frente de ondas
calculando la variación en la fase.
Así se ha obtenido mediante una representación cualquiera, la amplitud y la fase de un haz aberrado.
1000
amplitud 1000
fase
500 500
y(µm)
y(µm)
0 0
500 500
Existen distintos pares (𝑛, 𝑚) de especial importancia en óptica cuyo término de Zernike asociado recibe nombre
propio. Estos son:
(0, 0) Pistón.
(1, ±1) Tilt.
(2, −2) Astigmatismo.
(2, 0) Desenfoque.
(4, 0) Aberración Esférica.
A continuación se muestra una representación en fase y amplitud de las aberraciones más importantes
(1,-1) (1,1)
• circulo() Simula una máscara circular con transmitancia en el interior del círculo.
• anillo() Define una máscara anular con transmitancia únicamente en la corona del anillo, no per-
mitiendo el paso de luz ni en el centro ni en el exterior del anillo.
• cruz() Simulación de una máscara con forma de cruz con transmitancia unidad en el interior de la
misma.
• dosNiveles() Discretiza una imagen en dos niveles de intensidad definidos.
• imagen() Permite modificar algunas propiedades de imágenes tales como normalizar su intensidad,
seleccionar un calar de color, invertir la imagen, entre otros.
• escalaGrises() Construye una escala de grises.
• lente() Determina la amplitud y la fase de la luz al atravesar una lente.
• axicon() Modela una lente tipo axicon.
• redForked() Modela una red tipo Forked.
• rugosidad() Simula una superficie rugosa.
Las máscaras están guardados en una clase, muy sencilla, que sirve de repositorio de funciones, cada una de ellas,
rellena la variable self.u que es la matriz donde se almacena la máscara en forma de coeficientes de transmisión
t(x,y).
Adicionalmente a las máscaras, se incluyen 4 operaciones sobre las máscaras (__add__, __sub__, __mul__, __ro-
tar__). Las tres primeras son operaciones son predefinidas de la clase y actuan sobre los simbolos +,-,*.
Las máscaras que se han definido dentro de la clase mascarasXY se muestran a continuación y representan distintos
tipos de máscaras bidimensionales que habitualmente se emplean en óptica, tales como rendijas, dobles rendijas,
máscaras cuadradas, circulares, anillo, en cruz, entre otras.
La mayoría de las máscaras definidas tienen la forma:
def nombre(self, r0, parametros, angulo):
En este caso r0 representa la posición (x,y) de la máscara, parametros son los parámetros propios de la máscara,
como el tamaño, y angulo es la rotación.
Para facilitar la creación de nuevas máscaras, se han incluido las operaciones sobre las máscaras __add__,
__sub__, __mul__, __rotar__, ya definidas, las cuales respectivamente, suman, restan multiplican y rotan. Para
la definición de estas funciones se requiere de la clase macarasXY y camposXY.
Hay otra operación __rotar__ (no se implementa a través de un operador) y que se utiliza en las definiciones de
las máscaras para rotarlas.
Sumar Máscaras
En este primer ejemplo veremos la suma de dos mascaras una cuadrada y otra circular. El código para definir
este tipo de máscaras se explica más abajo. La estructura de la implementación en python consiste en declarar
las dos máscaras, t1, t2 y posteriormente sumarlas t2=t2+t1. Finalmente se dibujan a fin de obtener una
representación.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
import sys
sys.path.append(’../’)
from mascarasXY import *
def sumar_mascaras():
ndatos = 128
tamano = 250 * um
x = sp.linspace(-tamano / 2, tamano / 2, ndatos)
y = sp.linspace(-tamano / 2, tamano / 2, ndatos)
wavelength = 0.6328 * um
#Mascara 1
t1 = mascaraXY(x, y, wavelength)
t1.cuadrado(r0 = (-50 * um, 0 * um), size = (50 * um, 50 * um), angulo = 0 * grados)
#Mascara 2
t2 = mascaraXY(x, y, wavelength)
t2.circulo(r0 = (50 * um, 0 * um), radius = (25 * um, 25 * um), angulo = 0 * grados)
#suma
t3 = t2 + t1
#funcion para dibujar varias mascaras a la vez
dibujar_varios_campos(campos=(t1,t2,t3),titulos=(’mascara 1’,’mascara 2’, ’suma’))
sumar_mascaras()
plt.show()
En este primer ejemplo incluimos todo el código del archivo. A partir de ahora solo incluiremos la función. Es
decir, quitaremos el cabecero, las importaciones necesarias, que en estos ejemplos son
import sys
sys.path.append(’../’)
from mascarasXY import *
Hay que tener cuidado qué ocurre cuando las dos máscaras solapan: el coeficiente de transmisión no puede ser
superior a 1 (no son medios activos). Si nos fijamos en mascaraXY.__add__ esto está considerado.
Restar Máscaras
Con dos máscaras también se puede quitar la parte común entre ellas. El procedimiento a seguir en python es
análogo al caso de la suma. Nuevamente las máscaras cuadrada y circular se definirán más abajo en esta sección.
Rotar máscaras
En este ejemplo se genera un rectángulo (ya veremos como) y se cambia su orientación representando así máscaras
rectangulares a 0, 45 y 90 grados. Nuevamente las máscaras cuadrada y circular se definirán más abajo en esta
sección.
60
0 grados 60
45 grados 60
90 grados
40 40 40
20 20 20
0 0 0
20 20 20
40 40 40
60 60 60
60 40 20 0 20 40 60 60 40 20 0 20 40 60 60 40 20 0 20 40 60
El uso de la clase máscara se puede ver en el módulo correspondiente. Aquí pasamos a ver diversas máscaras que
luego se pueden utilizar en difracción y en otros ejemplos de óptica.
12.3.3 Rendija
Una rendija consiste en una región del plano de la máscara comprendida entre dos rectas paralelas, tal que en
dicha región se permite el paso de la luz, fuera de esta zona, la luz no atraviesa la máscara. Así dentro de la clase
mascarasXY se ha definido la función rendija. En ella se dan de argumentos el centro de la misma x0, la
anchura de la rendija um y el ángulo de inclinación en grados. El código define dos rectas verticales xmin y max,
cuyo espacio abarcado determina el paso de luz, la rendija. Estas dos rectas son verticales, por ello se aplica la
rotación para describir la inclinación de la rendija. Finalmente el if, determina la región del plano que puede ser
atravesada por la luz.
A modo de ejemplo se muestra la representación de una rendija con una anchura 100 um.
1.0
100 0.9
0.8
50 0.7
0.6
y(µm)
0 0.5
0.4
50 0.3
0.2
100 0.1
Análogamente con el caso de una rendija, se puede definir una doble rendija como una composición o suma de dos
rendijas simples. El estudio de dobles rendijas es de vital importancia en Óptica constituyendo una de las bases
de la difracción y a interferometría. Obsérvese como el código Python de la función doblerendija consiste en una
suma de dos rendijas simples separadas una distancia definida por el argumento separación.
Así se obtiene un ejemplo de representación de la doble rendija:
1.0
10 0.9
0.8
5 0.7
0.6
y(µm)
0 0.5
0.4
5 0.3
0.2
10 0.1
10 5 0 5 10 0.0
x(µm)
12.3.5 Cuadrados y rectángulos
Otro tipo de máscara de amplio uso en óptica son las máscaras cuadradas o rectangulares. Estas máscaras con-
sisten en una región del espacio comprendida entre cuatro rectas paralelas dos a dos, tales que los ángulos que
forman son todos rectos, de modo que la transmitancia en cualquier punto interior a dicha región es la unidad. La
implementación del código de python supone la definición del centro del cuadrado/rectángulo, así como el tamaño
de sus lados. Finalmente se define una rotación y los puntos de transmitancia unidad.
Su representación queda así
cuadrado rectangulo
100 100
50 50
0 0
50 50
100 100
100 50 0 50 100 100 50 0 50 100
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
De manera análoga a la máscara cuadrada, se define una máscara circular como aquella que permite el paso de luz
para aquellos rayos que incidan en el interior del círculo o elipse. Para ello se define el origen del círculo con r0
y los semiejes de la elipse radiox y radioy, en caso de ser iguales, se obtiene un círculo. La línea de código
ipasa = (Xrot - x0) * * 2 / radiusx * * 2 + (Yrot - y0) * * 2 / radiusy * * 2 < 1
circulo elipse
100 100
50 50
0 0
50 50
100 100
100 50 0 50 100 100 50 0 50 100
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
12.3.7 Anillo
Una estructura tipo anillo consiste en dos regiones circulares o elípticas concéntricas, tales que el paso de luz
sucede únicamente en la región comprendida entre ambas. Para su implementación se definen dos vectores con
dos compontes cada uno, llamados radio1 y radio2 tales que sus componentes representan los semiejes de
las elipses interior y exterior. Nuevamente el ángulo theta describe la inclinación del anillo. Por tanto se definen
dos círculos o elipses concéntricos tales que se permite el paso de luz en su interior, estos están binarizados, es
una máscara o matriz con 1 en aquellos puntos interior y cero en los exteriores. Para construir la estructura tipo
anillo, se restan ambos círculos (máscaras), de este modo que la corona es la única región con unos en su interior.
Como resultado se tiene
50 50
0 0
50 50
100 100
100 50 0 50 100 100 50 0 50 100
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
También se pueden hacer figuras más interesantes si no se mantiene que el tamaño del anillo 2 sea mayor que el
anillo uno, como en el segundo ejemplo, donde
t1.anillo(r0 = (0 * um, 0 * um), radius1 = (50 * um, 100 * um), radius2 = (100 * um, 50 * um), ang
12.3.8 Cruz
Las máscaras tipo cruz se pueden interpretar como la superposición de dos máscaras tipo cuadrado rotadas entre
sí 90º permitiento el paso de luz únicamente en la región interior a la cruz. Por tanto para la implementación de
este tipo de máscara, bastará con definir las dos máscaras cuadradas, mediante su origen (común) r0 y los lados
de cada cuadrado. De este modo se definen t1 y t2 como dos máscaras cuadradas rotadas entre sí 90º. La suma de
las matrices de cada máscara, da lugar a la máscara de cruz, no obstante hay puntos en la matriz suma t1+t2 con
transmitancia mayor que la unidad por ello en la última línea de código se redefinen todos los puntos con valor
mayor a uno, reescalándolos a la unidad.
Así queda representada como
1.0
100 0.9
0.8
50 0.7
0.6
y(µm)
0 0.5
0.4
50 0.3
0.2
100 0.1
Esta función discretiza una imagen en dos niveles de intensidad definidos por los argumentos nivel1 y nivel2. Esta
discretización se hace acorde a un nivel de corte o de referencia, xcorte, de modo que todos aquellos puntos de
la imagen con intensidad menor xcorte adquieren el valor nivel1, así mismo, los niveles mayores a xcorte
son asignados con intensidad nivel2. La estructura del programa realiza este proceso en dos líneas, la primera
crea una matriz de nivel1 con el mismo tamaño que la imagen, X. La segunda línea asigna la intensidad nivel2
a aquellas columnas de la matriz con nivel de intensidad mayor que el nivel de corte, xcorte.
Así resulta
0.250
100 0.225
0.200
50 0.175
0.150
y(µm)
0 0.125
0.100
50 0.075
0.050
100 0.025
Si no sabemos crear una máscara, o resulta demasiado complicada la podemos dibujar en otro programa, como
Gimp, Inkscape. Entonces la podemos cargar como máscara a través de la función imagen. Para ello necesi-
tamos la librería Image específica de python para trabajar con imágenes. Posteriormente se carga la imagen‘
especificada con el argumento nombre, para ello se hace uso de dos funciones específicas cargarImagen y
visualizarimagen, las cuales cargar una imagen desde un archivo con localización específica, y la visuali-
zan respectivamente. No entramos a explicar el funcionamiento de estas dos funciones, estando definidas en una
clase externa imagenesLM, y sin poseer mayor dificultad. Image.open(nombre) abre la imagen pero no la
muestran en pantalla, sino que la almacena como variable im, recuérdese que la imagen ya está siendo visualizada,
puesto que se empleó visualizarImagen, en la línea anterior. im.transpose(1), transpone la imagen,
intercambiando filas por columnas. Mediante im.split() se descompone la imagen en colores extrayendo un
array con las componentes RGB de la imagen. Debido a que no interesa el color sino la intensidad, se selecciona la
componente de color canal especificada en el argumento de nuestra función imagen con colores[canal].
El resto del programa son if y suponen una serie de condiciones. El primero, cambia el tamaño de la imagen
manteniendo la relación de aspecto permitiendo además una rotación de la imagen. Para ello se extraen las di-
mensiones de la imagen y se definen dos arrays para finalmente generar un mallado y aplicar una rotación. El
siguiente if plantea una inversión de intensidad, ello lo logra restando la intensidad máxima de la imagen menos
la intensidad de la imagen en cada punto y generando así una imagen invertida en intensidades. Finalmente, el
último if, normaliza las intensidades a la unidad.
Veamos un ejemplo de sencillo de cómo cargar una imagen.
1.0
100 0.9
0.8
50 0.7
0.6
y(µm)
0 0.5
0.4
50 0.3
0.2
100 0.1
La función escalaGrises construye un vector con el número de niveles de gris establecidos en el argumento
NumNiveles, con intensidades máxima nivelMax y mínima nivelMin. Para ello se define un vector t de
ceros a fín de generar la estructura de nuestro vector salida, sobre este vector sobrescribiremos los datos finales.
Posteriormente, se define un array, niveles con intensidades entre nivelMin y nivelMax del tamaño del vector
x definido en la clase mascarasXY. Así mismo, otro vector xpos de números enteros con valores entre 0 y el
entero más próximo a la longitud de x, con numNiveles + 1 componentes. El objetivo de este vector será de-
terminar las filas de t a las que se asocia cada nivel de gris. Esto último se logra mediante el bucle for, asociando
a las componentes de t comprendidas entre los valores de dos compontes consecutivas de xpos el valor de gris
especificado.
De este modo se obtiene la figura
1.0
100 0.9
0.8
50 0.7
0.6
y(µm)
0 0.5
0.4
50 0.3
0.2
100 0.1
Mediante la función lente calculamos la fase y la amplitud de la luz al atravesar una lente con astigmatismo. Así
sean f1 y f2 las focales, de modo que la fase, 𝜑 de la luz viene determinada por
Así mismo, la transmitancia se supone determinada por la superficie de la lente, la cual modelamos mediante
una máscara circular. Por tanto se define el centro de la lente, con r0 y la focal f1, f2, para definir la máscara
circular/elíptica con orientación determinada por el ángulo especificado en el argumento de la función conforme
se describió en la sección de la máscara círculo. De este modo, queda definida la amplitud mediante la máscara.
Finalmente, para la fase basta implementar la ecuación anterior.
En los siguientes ejemplos se muestra una representación de la amplitud y la fase queda
amplitud fase
400 400
200 200
y(µm)
y(µm)
0 0
200 200
400 400
amplitud fase
400 400
200 200
y(µm)
y(µm)
0 0
200 200
400 400
12.3.13 Axicón
Un axicón es un tipo particular de lente con superficie cónica. Su función principal es la de transformar haces co-
limados en anillos. La función axicon con argumentos, radio y altura del cono, así como índice de refracción
del vidrio, calcula la intensidad y la fase de la luz al atravesar dicho elemento óptico. Así una vez definidos el
origen r0, el vector de onda k y la distancia, r, de la generatriz al eje del cono, se implementa la superficie del
elemento óptico como h en términos de r. Definida la superficie del axicon, queda imponer una serie de restric-
ciones tales como que la altura, h no puede ser nunca negativa, esto es, el axicon, no se extiende en el espacio
objeto. Así mismo, se determina la región de transmitancia de la luz como aquella en la que incide la luz sobre el
axicon, es decir, únicamente son válidos los puntos de la base del axicon, r<radius. Finalmente se determina
la fase con la relación
..:math:: e^{ik(n-1)h}
En el ejemplo se muestra la amplitud y la fase a la salida de un axicon
amplitud fase
100 100
50 50
y(µm)
y(µm)
0 0
50 50
100 100
100 50 0 50 100 100 50 0 50 100
x(µm) x(µm)
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 2.4 1.6 0.8 0.0 0.8 1.6 2.4
Se muestra cómo desarrollar la espiral de Arquímedes. Resulta un poco complicado entender como se desarrolla
(yo tampoco lo entiendo),
0 0
50 50
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
Esta función hace uso de las clases fuentesXY así como de máscaras. Por defecto la
EspiralLaguerreGauss obtiene la espiral en amplitud, salvo que se especifique lo contrario. Se im-
porta la clase fuentesXY, y se genera un haz tipo Laguerre conforme se vio en la sección de fuentes. A
continuación definimos el radio del haz que se va a representar siendo este la mitad del cuadrado de represen-
tación. Resta implementar la espiral, para ello se llama a la clase mascarasXY, y se implemente la función
círculo ya definida anteriormente. Posteriormente se extrae la fase con angle y se normalizan las intensidades,
finalmente se realiza un binarización. Alternativamente se puede especificar la fase en el argumento de la función,
el último if es el encargado de esta tarea.
1.0
100 0.9
0.8
50 0.7
0.6
y(µm)
0 0.5
0.4
50 0.3
0.2
100 0.1
amplitud fase
100 100
50 50
y(µm)
y(µm)
0 0
50 50
100 100
100 50 0 50 100 100 50 0 50 100
x(µm) x(µm)
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.4 0.8 1.2 1.6 2.0 2.4 2.8
Una red tipo Forked es un tipo de máscara binaria de tipo franjas en la que existe una macla, punto en el cual un
conjunto de franjas convergen. Este tipo de red se puede definir mediante el siguiente código.
El resultado es
1.0
100 0.9
0.8
50 0.7
0.6
y(µm)
0 0.5
0.4
50 0.3
0.2
100 0.1
La superficies rugosas son de gran importancia práctica en Óptica pues muchas de las superficies tienen un cierto
grado de rugosidad. La rugosidad es un proceso aleatorio que se caracteriza a través de ciertos parámetros como
la altura media y la longitud de correlación, que puede ser distinta en las direcciones x e y, por los procesos de
fabricación de las superficies. En el siguiente código se muestra un ejemplo de cómo generar una superficie rugosa
con unos parámetros dados.
La fase y amplitud quedan representadas como sigue
100 2.4
1.6
50
0.8
y(µm)
0 0.0
0.8
50
1.6
100 2.4
100 50 0 50 100
x(µm)
topografia
1.6
100
1.2
50 0.8
0.4
y(µm)
0
0.0
50 0.4
0.8
100
1.2
100 50 0 50 100
x(µm)
ejemplos de visualización 3D
191
PIMCD2013-python, Publicación 1.0
13.1 Introducción
donde E0 (𝜉, 𝜂) = 𝑡(𝜉, 𝜂)E𝑖𝑛𝑐 (𝜉, 𝜂) es el campo a la salida del elemento difractor que, bajo la aproximación
de elemento delgado se obtiene como la multiplicación del campo incidente E𝑖𝑛𝑐 (𝜉, 𝜂) por el coeficiente de
transmisión de la máscara difractora 𝑡(𝜉, 𝜂)
13.2 Programación
Para el desarrollo de los ejemplos de esta sección hemos programado numéricamente el cálculo de esta integral a
partir de 'fast-Fourier-transform based direct integration (FFT-DI) method' según el artículo de Shen
y Wang 1 . Este algoritmo lo hemos implementado en la clase camposXY (que es heredada por mascarasXY y
fuentesXY) con el nombre de RS (a partir de Rayleigh-Sommerfed, que es una versión ligeramente mejorada de
la integral de Fresnel).
Por consiguiente, si tenemos un campo, máscara o fuente, tendrá un método RS que podemos aplicar. Si tenemos
un campo de tamaño NxM, el resultado de la propagación será también NxM, aunque existe un parámetro de
amplificación que nos permite calcular tamaños del plano de observación mayores (jN)x(jM).
La ventaja que tiene es que nos devuelve un parametro de calidad que nos informa si la aproximación se ha
realizado debidamente. Si el parámetro de calidad (almacenado en self.calidad) tiene un valor superior a 1, la
propagación se ha realizado convenientemente.
def RS(self, z = 10 * mm, newField = True, tipo=’z’, xout = None, yout = None):
"""Metodo de Fast-Fourier-Transform para la integracion numerica
de la Formula de la difraccion de Rayleigh-Sommerfeld.
Extraido de Applied Optics vol 45 num 6 (1102-1110) - 2006
- z es la distancia (si es <0 se realiza la propagacion inversa
- newfield, si es False el calculo se queda en la instancia, si es True se genera
- xout, yout se utilizan para decir las coordenadas de salida (para la amplificaci
el tamaño del pixel debe ser igual, aunque la máscara puede tener distinto tamaño
"""
if xout == None:
xout = self.x
if yout == None:
yout = self.y
nx = len(xout); ny = len(yout)
1 Fabin Shen and Anbo Wang "Fast-Fourier-transform based numerical integration method for the RayleighSommerfeld diffraction
#parametro de calidad
dr_real = sp.sqrt(dx ** 2 + dy ** 2)
rmax = sp.sqrt((xout ** 2).max() + (yout ** 2).max())
dr_ideal = sp.sqrt(self.wavelength ** 2 + rmax ** 2 + 2 * self.wavelength * sp.sqr
self.calidad = dr_ideal / dr_real
#siempre que se hace un cálculo se informa por consola de la calidad de este.
if(self.calidad > 1):
print ’Buena aproximacion: factor ’, self.calidad
else:
print ’Necesita un muestreo mayor: factor’, self.calidad
#matriz computada
W = 1
U = sp.zeros((2 * ny - 1, 2 * nx - 1), dtype = complex)
U[0:ny, 0:nx] = W*self.u #el transpose lo he cambiado porque daba problemas para
xext = x1 - xin[::-1] #da la vuelta
xext = xext[0:-1]
xext = sp.concatenate((xext, self.x - xin1))
yext = y1 - yin[::-1]
yext = yext[0:-1]
yext = sp.concatenate((yext, self.y - yin1))
Xext, Yext = sp.meshgrid(xext, yext)
#los calculos se pueden dejar en la instancia o crear un nuevo campo (más usual).
if newField == True:
campoSalida = campoXY(self.x, self.y, self.wavelength)
campoSalida.u = Usalida / z
campoSalida.calidad=self.calidad
return campoSalida
else:
self.u = Usalida / z
Todos los cálculos realizados en esta sección serán númericos, lo cual nos da una gran ventaja computacional:
solamente tenemos que describir la fuente y la máscara, y tendremos el campo difractado a una distancia z.
Como primer ejemplo realizaremos la propagación en campo cercano de una abertura bidimensional cuadrada.
Utilizaremos este ejemplo para explicar el proceso.
Se definen los parámetros dimensionales de las máscaras y los parámetros ópticos (longitud de onda).
Se crea una fuente de iluminación a través de la clase fuenteXY().
Se crea una máscara con las dimensiones apropiadas.
Se realiza la Aproximación de elemento delgado: u2 = u1 * t1
se realiza la propagación: u3 = u2.RS(z = z_difraccion, newField = True)
Se dibuja el resultado.
import sys
sys.path.append(’../’)
sys.path.append(’../general/’)
def difraccion_cuadrado():
#tamano de area de visualizacion
tamano = 100 * um
ndatos = 256
x0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
y0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
#longitud de onda
lambda0 = 0.6238 * um
#fuente de iluminacion
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0);
u1.onda_plana(A = 1, theta = 0 * grados, phi = 0 * grados)
#mascara
radio = 20 * um
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.cuadrado(r0 = (0 * um, 0 * um), size = (60 * um, 60 * um), angulo = 0 * grados)
u2 = u1 * t1
#plano de observacion
z_difraccion = 500*um
dibujar_varios_campos(campos=(u2,u3),titulos=(’cuadrado’, texto))
difraccion_cuadrado()
plt.show()
cuadrado z=500 µm
40 40
20 20
0 0
20 20
40 40
40 20 0 20 40 40 20 0 20 40
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
u2 = u1 * t1
return u2
plt.figure(figsize=(12,6))
num_figuras = len(distancias)
filas = [1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 4, 4, 4, 4, 5, 4]
columnas = [1, 2, 3, 2, 3, 3, 3, 3, 3, 5, 3, 3, 4, 4, 3, 4]
extension=[u2.x[0],u2.x[-1],u2.y[0],u2.y[-1]]
plt.subplot(filas[num_figuras], columnas[num_figuras], 1)
El campo total se genera llamando a las dos funciones secuencialmente, transmitiendo el valor u2 de una a la otra
función:
u2=mascara_cuadrado()
difraccion_varias_posiciones(u2, distancias = sp.linspace(25 * um, 2* mm, 5))
plt.show()
Eventualmente, si la distancia de propagación fuera mucho mayor que la distancia de Fresnel, 𝑧 >> 𝑎2 /𝜆, el
campo difractado sería la transformada de Fourier del objeto (pues estamos iluminando con onda plana), en este
caso una función sinc.
Una máscara de gran importancia en la óptica es la abertura circular. Según hemos definido la clase mascarasXY,
para determinar el campo generado por la máscara circular, los ejemplos anteriores son completamente válidos, y
solamente es necesaio intercambiar la función de creación de la máscara
t1.circulo(r0 = (0 * um, 0 * um), radius = (radio, radio))
circulo z=1282 µm
40 40
20 20
0 0
20 20
40 40
40 20 0 20 40 40 20 0 20 40
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
1.5
Intensidad
1.0
0.5
40 20 0 20 40
h (perfil)
Para este ejemplo se ha elegido que la distancia sea exactamente la mitad de la distancia de Fresnel
t1.circulo(r0 = (0 * um, 0 * um), radius = (radio, radio))
#distancia de fresnel
z_fresnel = radio ** 2 / lambda0
#plano de observacion
z_difraccion = .5 * z_fresnel
import sys
sys.path.append(’../’)
sys.path.append(’../general/’)
plt.figure(figsize=(12,8))
num_figuras = len(distancias)
filas = [1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 4, 4, 4, 4, 5, 4]
columnas = [1, 2, 3, 2, 3, 3, 3, 3, 3, 5, 3, 3, 4, 4, 3, 4]
extension=[u2.x[0],u2.x[-1],u2.y[0],u2.y[-1]]
plt.subplot(filas[num_figuras], columnas[num_figuras], 1)
h1 = plt.imshow(abs(u2.u) ** 2, interpolation = ’bilinear’,
origin = ’lower’, extent = extension)
h1.set_cmap("gist_heat")
plt.title(u’circle’)
def mascara_circulo():
tamano = 100 * um
ndatos = 256
num_figuras = 3
z_fresnel = (tamano) ** 2 / lambda0
distancias_difraccion = sp.linspace(25 * um, .25 * z_fresnel, num_figuras)
u2 = u1 * t1
return u2
u2=mascara_circulo()
difraccion_varias_posiciones(u2, distancias = sp.linspace(25 * um, 1* mm, 3))
plt.show()
En este epígrafe se considera como elemento difractor un borde. Así se considera como máscara una máscara
binaria, y se calcula la propagacióna una distancia de 25 um de la máscara. El proceder es análogo a una máscara
cuadrada.
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Ejemplos de difraccion en campo lejano
#-------------------------------------
import sys
sys.path.append(’../’)
def difraccion_borde():
#tamano de area de visualizacion
tamano = 100 * um
ndatos = 256
x0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
y0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
#longitud de onda
lambda0 = 0.6238 * um
#fuente de iluminacion
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0);
u1.onda_plana(A = 1, theta = 0 * grados, phi = 0 * grados)
#mascara
#plano de observacion
z_difraccion = 25*um
difraccion_borde()
plt.show()
circulo z=25 µm
40 40
20 20
0 0
20 20
40 40
40 20 0 20 40 40 20 0 20 40
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
1.4
1.2
1.0
Intensidad
0.8
0.6
0.4
0.2
15 10 5 0 5 10
h (perfil)
Debido a la difracción, la luz al propagarse se introduce en la sombra, como se observa en el perfil.
Veamos que ocurre a varias distancias. A medida que nos separamos del borde, más luz entra en la zona de sombra
y mas perceptibles son los efectos difractivos
#!/usr/local/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
import sys
sys.path.append(’../’)
sys.path.append(’../general/’)
plt.figure(figsize=(12,6))
num_figuras = len(distancias)
filas = [1, 1, 1, 2, 2, 2, 3, 3, 3, 2, 4, 4, 4, 4, 5, 4]
columnas = [1, 2, 3, 2, 3, 3, 3, 3, 3, 5, 3, 3, 4, 4, 3, 4]
plt.subplot(filas[num_figuras], columnas[num_figuras], 1)
h1 = plt.imshow(abs(u2.u) ** 2)
h1.set_cmap("gist_heat")
plt.title(u’mascara’)
def mascara_borde():
tamano = 100 * um
ndatos = 256
x0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
y0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
lambda0 = 0.6238 * um
num_figuras = 8
z_fresnel = (tamano) ** 2 / lambda0
distancias_difraccion = sp.linspace(25 * um, .25 * z_fresnel, num_figuras)
u2 = u1 * t1
return u2
u2=mascara_borde()
difraccion_varias_posiciones(u2, distancias = sp.linspace(10 * um, 100* um, 2))
plt.tight_layout()
plt.show()
En este caso el elemento difractor es una doble rendija. Se calcula la propagación a varias disntancias talesque la
distribución de intensidad no está en la posición de la zona clara, sino en la zona opaca, en contradicción con la
teoría geométrica (cosas que tienen los efectos difractivos), es decir entre las dos rendijas.
#!/usr/local/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
import sys
sys.path.append(’../’)
sys.path.append(’../general/’)
def difraccionDobleRendija():
tamano = 250 * um
numdatos = 128
x0 = sp.linspace(-tamano / 2 , tamano / 2, numdatos)
y0 = sp.linspace(-tamano / 2 , tamano / 2, numdatos)
lambda0 = 0.6238 * um
z = 4 * mm
u2 = u1 * t
u3 = u2.RS(z=z, newField = True)
difraccionDobleRendija()
plt.show()
50 50
0 0
50 50
100 100
100 50 0 50 100 100 50 0 50 100
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
Nótese como aunque la óptica geométrica predice la no existencia de luz en dicha región, experimentalmente (en
este caso simulación) se observa un patrón de intensidad en dicha región. Este muestra como la difracción es la
capacidad de la luz para sortear bordes y agujeros.
Otro ejemplo es la difracción por una lente, de la cual mostramos la intensidad y la fase. Debido a que la lente
se ha suspuesto rotacionalmente simétrica, el patrón de difracción es perfectamente simétrico. Así se muestra un
perfil de intensidad.
#!/usr/local/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
import sys
sys.path.append(’../’)
sys.path.append(’../general/’)
def difraccion_lente():
tamano = 200 * um
numdatos = 256
x0 = sp.linspace(-tamano / 2 , tamano / 2, numdatos)
y0 = sp.linspace(-tamano / 2 , tamano / 2, numdatos)
lambda0 = 0.6238 * um
t = t1 * t2
t.dibujar(tipo = ’campo’)
u3 = u1 * t
u3 = u3.RS(z = 2 * mm, newField = True)
u3.dibujar(tipo = ’intensidad’)
difraccion_lente()
plt.show()
100
amplitud 100
fase
50 50
y(µm)
y(µm)
0 0
50 50
100 400
350
50 300
250
y(µm)
0 200
150
50 100
50
100100 50 0 50 100 0
x(µm)
400
300
Intensidad
200
100
0 30 20 10 0 10 20
h (perfil)
13.9 Difracción por una lente binaria
En este último ejemplo se supone una lente de Fresnel. Este tipo de lentes es puramente difractiva. Así se obtiene
#!/usr/local/bin/python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
import sys
sys.path.append(’../’)
from camposXY import * #@UnusedWildImport
from fuentesXY import fuenteXY
from mascarasXY import mascaraXY
def difraccion_lente_binaria():
tamano = 200 * um
numdatos = 256
x0 = sp.linspace(-tamano / 2 , tamano / 2, numdatos)
y0 = sp.linspace(-tamano / 2 , tamano / 2, numdatos)
lambda0 = 0.6238 * um
t = t1 * t2
t.dibujar(tipo = ’campo’)
u3 = u1 * t
u3 = u3.RS(z = 2 * mm, newField = True)
u3.dibujar(tipo = ’intensidad’)
u3.dibujar_perfil(punto1=(-30*um, 0),punto2=(30*um,0), tipo=’intensidad’)
difraccion_lente_binaria()
plt.show()
100
amplitud 100
fase
50 50
y(µm)
y(µm)
0 0
50 50
100
160
140
50
120
100
y(µm)
0 80
60
50 40
20
100100 50 0 50 100 0
x(µm)
150
Intensidad
100
50
0 30 20 10 0 10 20
h (perfil)
209
PIMCD2013-python, Publicación 1.0
14.1 Introducción
𝜋𝑎2
𝑧≫
𝜆
donde a es el tamaño máximo del elemento difractor. Bajo esta aproximación, el campo difractado se puede
calcular como una integral
𝑥2 +𝑦 2
𝑒𝑖𝑘(𝑧+ 2𝑧 ) ∫︁ ∫︁
𝑘
E(𝑥, 𝑦, 𝑧) = E0 (𝜉, 𝜂)𝑒−𝑖 𝑧 (𝑥𝜉+𝑦𝜂) 𝑑𝜉𝑑𝜂.
𝑖𝜆𝑧
La gran ventaja de esta ecuación es que se puede escribir como una transformada de Fourier, que es una operación
bastante conocida en matemáticas
𝑥2 +𝑦 2
𝑒𝑖𝑘(𝑧+ 2𝑧 )
E(𝑥, 𝑦, 𝑧) = 𝑇 𝐹 [E0 (𝜉, 𝜂)] .
𝑖𝜆𝑧
Como el primer término delante de la transformada de Fourier es casi constante para un plano z determinado, se
suele eliminar.
Vamos a calcular mediante implementación de código de Python el campo difractado a partir de una máscara
cuadrada. Se observa el perfil con la típica figura de 𝑠𝑖𝑛𝑐2 (𝑥)
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Ejemplos de difraccion en campo lejano
#-------------------------------------
def difraccion_cuadrado():
#Se carga la clase fuentes
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0)
#Condicion de onda plana
u1.onda_plana(A = 1, theta = sp.pi / 2, phi = 0 * grados)
#Trabajaremos con la clase mascaras
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
#Mascara cuadrada
t1.cuadrado(r0 = (0 * um, 0 * um), size = (15 * um, 15 * um), angulo = 0 * grados)
#Campo por mascara
u2 = u1 * t1
#Transformada de Fourier
u3 = u2.fft(quitar0 = False)
#Representacion
dibujar_varios_campos(campos=(t1,u3), titulos=(’mascara’,’campo lejano’), logaritmico=True
plt.axis([u3.x.min()/3, u3.x.max()/3, u3.y.min()/3, u3.y.max()/3])
punto1=(u3.x.min(),0)
punto2=(u3.x.max(), 0)
u3.dibujar_perfil(punto1,punto2, normalizar=’maximo’)
difraccion_cuadrado()
plt.show()
1000
100
0 0
100
1000
200
2000
200 100 0 100 200 2000 1000 0 1000 2000
1.0
0.8
Intensidad
0.6
0.4
0.2
En este ejemplo calcular el campo difractado Por una rendija. Es muy parecido al cuadrado, pero en una de las
direcciones colapsa a una línea, pues la transformada de Fourier de un campo plano (en la dirección y) es una delta
de Dirac.
def difraccion_rendija():
#Clase fuentes
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0)
#Iluminacion de onda plana
u1.onda_plana(A = 1, theta = sp.pi / 2, phi = 0 * grados)
#Clase mascaras
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
#Rendija
t1.rendija(x0 = 0, size = 5 * um, angulo = 0 * grados)
#Campo transmitido
u2 = u1 * t1
#Campo difractado
u3 = u2.fft(quitar0 = False)
#Representacion
dibujar_varios_campos(campos=(u2, u3),titulos=(’mascara’, ’fft’),
titulo=’tamano’, logaritmico = True, normalizar = ’maximo’ )
plt.set_cmap("gray")
#perfiles
punto1=(u3.x.min()/2,0)
punto2=(u3.x.max()/2,0)
Tras ejecutar el código se obtiene la máscara y el patrón de intensidades, así mismo se muestra un perfil de
intensidad.
50 5000
0 0
50 5000
100 10000
100 50 0 50 100 10000 5000 0 5000 10000
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
1.0
0.8
Intensidad
0.6
0.4
0.2
Se calcula el campo difractado por una máscara circular. El resultado muestra el perfil con la típica figura de
𝐽02 (𝑥).
def difraccion_circulo():
#Clase fuentes
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0)
#Iluminacion onda plana
u1.onda_plana(A = 1, theta = sp.pi / 2, phi = 0 * grados)
#Clase mascaras
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
#Mascara
t1.circulo(r0 = (0 * um, 0 * um), radius = (15 * um, 15 * um), angulo = 0 * grados)
#Campo transmitido
u2 = u1 * t1
#Campo difractado
u3 = u2.fft(quitar0 = False)
#Representacion
punto1=(u3.x.min()/5,0)
punto2=(u3.x.max()/5,0)
dibujar_varios_campos(campos=(t1,u3), titulos=(’mascara’,’campo lejano’), logaritmico=True
plt.axis([u3.x.min()/3, u3.x.max()/3, u3.y.min()/3, u3.y.max()/3])
u3.dibujar_perfil(punto1,punto2, normalizar=’maximo’)
Tras ejecutar el código se obtiene la máscara y el patrón de intensidades, así mismo se muestra un perfil de
intensidad.
mascara 2000
campo lejano
200
100 1000
0 0
100 1000
200
2000
200 100 0 100 200 2000 1000 0 1000 2000
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
0.8
Intensidad
0.6
0.4
0.2
Se muestra también la figura de difracción de una única rendija y se observa que es la envolvente de la doble
rendija: doble rendija = difracción de una rendija + interferencia entre dos rendijas.
def difraccion_doble_rendija():
#Clase fuentes
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0)
#Iluminacion onda plana
u1.onda_plana(A = 1, theta = sp.pi / 2, phi = 0 * grados)
#Doble rendija
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.dobleRendija(x0 = 0, size = 5 * um, separacion = 20 * um, angulo = 0 * grados)
#Campo transmitido y difractado 1
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
punto2=(u5.x.max(),0)
h, z_perfil, punto1, punto2=u5.perfil(punto1,punto2)
u3.dibujar_perfil(punto1,punto2, normalizar = ’maximo’)
plt.plot(h,z_perfil/z_perfil.max(),’r’,lw=2)
plt.legend((’doble rendija’, ’una rendija’))
plt.xlim(h.min()/3,h.max()/3)
Tras ejecutar el código se obtiene la máscara y el patrón de intensidades, así mismo se muestra un perfil de
intensidad. Nótese como la envolvente del patrón de intensidades en la pantalla es la intensidad de una única
rendija.
100100 15000
50 0 50 100 1500010000 5000 0 5000 1000015000
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
doble rendija
una rendija
0.8
Intensidad
0.6
0.4
0.2
Las estructuras más complejas tienen una distribución en el plano de Fourier también más complejo. En este
ejemplo la máscara ha sido una estrella, esta se ha cargado a partir de una imagen binarizada, por tanto no ha sido
programada o tomada de la clase máscaraXY.
def difraccion_estrella():
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0)
u1.onda_plana(A = 1, theta = sp.pi / 2, phi = 0 * grados)
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.imagen(nombre=’estrella_pequena.png’)
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
dibujar_varios_campos(campos=(u2,u3), titulos=(’mascara’,’fft’), \
logaritmico=True, normalizar=’maximo’)
mascara 3000
fft
200
2000
100
1000
0 0
1000
100
2000
200
3000
200 100 0 100 200 3000 2000 1000 0 1000 2000 3000
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
Las estructuras más complejas tienen una distribución en el plano de Fourier también más complejo, este es el
caso de una fotografía real, en la cual debido a los múltiples detalles, la transformada de Fourier se muestra como
un patrón del que podría decirse que carece de orden, sin embargo conserva la información de la imagen original.
def difraccion_lena():
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0)
u1.onda_plana(A = 1, theta = sp.pi / 2, phi = 0 * grados)
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.imagen(nombre=’lena.png’)
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
dibujar_varios_campos(campos=(u2,u3), titulos=(’mascara’,’fft’), \
logaritmico=True, normalizar=’maximo’)
mascara 3000
fft
200
2000
100
1000
0 0
1000
100
2000
200
3000
200 100 0 100 200 3000 2000 1000 0 1000 2000 3000
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
La red sinusoidal tiene solamente tres frecuencias espaciales, que son las que se observan en la figura de difracción.
def difraccion_red_seno():
#tamano de area de visualizacion
tamano = 1000 * um
ndatos = 512
x0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
y0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
#longitud de onda
lambda0 = 0.6238 * um
#fuente de iluminacion
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0);
u1.onda_plana(A = 1, theta = 0 * grados, phi = 0 * grados)
#mascara
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.red_seno(periodo=40*um)
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
u2.dibujar()
punto1=(u3.x.min(),0)
punto2=(u3.x.max(),0)
1.0
400 0.9
0.8
200 0.7
0.6
y(µm)
0 0.5
0.4
200 0.3
0.2
400 0.1
0.8
0.6
Intensidad
0.4
0.2
0.0
La red de Ronchi tiene más frecuencias espaciales, todas equidistanciadas, que son las que se observan en la figura
2
de difracción. La altura de cada pico es el cuadrado del valor del coeficiente de Fourier 𝐼𝑝𝑖𝑐𝑜,𝑛 = |𝑎𝑛 | .
def difraccion_red_ronchi():
#tamano de area de visualizacio
tamano = 1000 * um
ndatos = 512
x0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
y0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
#longitud de onda
lambda0 = 0.6238 * um
#fuente de iluminacion
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0);
u1.onda_plana(A = 1, theta = 0 * grados, phi = 0 * grados)
#mascara
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.red_ronchi( periodo = 40 * um, x0 = 0 * um, fillFactor = 0.5, angulo = 0 * grados)
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
u2.dibujar()
punto1=(u3.x.min(),0)
punto2=(u3.x.max(),0)
1.0
400 0.9
0.8
200 0.7
0.6
y(µm)
0 0.5
0.4
200 0.3
0.2
400 0.1
0.8
0.6
Intensidad
0.4
0.2
0.0
En esta sección se analizan las propiedades matemáticas de la transformada de Fourier, a traves de la observación
de la misma en el plano de Fourier.
En este epígrafe mostramos como desplazar un elemento difractivo, la distribución de intensidad es la misma (no
la fase). Esto matemáticamente se corresponde con la propiedad:
Así queda patente al ejecutar el siguiente código, el cual considera la difracción por una máscara cuadrada. Las
figuras muestras la máscara y su desplazamiento así como la obsercación en el plano de Fourier de sendas másca-
ras.
def difraccion_desplazamiento():
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0)
u1.onda_plana(A = 1, theta = sp.pi / 2, phi = 0 * grados)
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.cuadrado(r0 = (0 * um, 0 * um), size = (15 * um, 15 * um), angulo = 0 * grados)
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
u4 = u1 * t2
u5 = u4.fft(quitar0=False)
100 100
0 0
100 100
200 200
200 100 0 100 200 200 100 0 100 200
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
6000
x=0 um desplazamiento
6000
x=100*um
4000 4000
2000 2000
0 0
2000 2000
4000 4000
6000 6000
6000 4000 2000 0 2000 4000 6000 6000 4000 2000 0 2000 4000 6000
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
En este epígrafe se considera el mismo elemento difractivo del ejemplo anterior pero aplicándole una rotación, así
resulta una rotación de la transformada de Fourier.
def difraccion_rotacion():
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0)
u1.onda_plana(A = 1, theta = sp.pi / 2, phi = 0 * grados)
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.cuadrado(r0 = (0 * um, 0 * um), size = (15 * um, 15 * um), angulo = 0 * grados)
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
u4 = u1 * t2
u5 = u4.fft(quitar0=False)
100 100
0 0
100 100
200 200
200 100 0 100 200 200 100 0 100 200
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
Si el ángulo del incidencia es variado, la posición en el plano de Fourier del orden cero de difracción se ve
desplazada.
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Ejemplos de difraccion en campo lejano
#-------------------------------------
sys.path.append(’../’)
def difraccion_haz_inclinado(haz_incidente):
u1=haz_incidente
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.cuadrado(r0 = (0 * um, 0 * um), size = (15 * um, 15 * um), angulo = 0 * grados)
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
return u2, u3
plt.show()
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
Se compara la difracción por un máscara cuadrada bajo dos fuentes de iluminación, un haz de entrada en primer
lugar considerado una onda plana y en segudno, una onda gaussiana con un tamaño comparable al elemento
difractor. El resultado, muestra como en el caso del haz gausiano, la mayor parte de la energía del haz difractado
se concentra en los órdenes más bajo de difracción. Mientras que en el caso de una onda plana, la energía está más
repartida entre los distintos órdenes.
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#----------------------------------------------------------------------
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Breaje
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Ejemplos de difraccion en campo lejano
#-------------------------------------
def difraccion_rectangulo_onda_plana(haz_incidente):
u1=haz_incidente
u2 = u1 * t1
u2.dibujar(tipo = ’intensidad’, logaritmico = False, normalizar = ’maximo’)
plt.axis([-15*um,15*um,-15*um,15*um])
u3 = u2.fft(quitar0 = False)
return u2, u3
plt.show()
15 1.0
0.9
10
0.8
0.7
5
0.6
y(µm)
0 0.5
0.4
5
0.3
0.2
10
0.1
1515 10 5 0 5 10 15 0.0
x(µm)
15 1.0
0.9
10
0.8
0.7
5
0.6
y(µm)
0 0.5
0.4
5
0.3
0.2
10
0.1
1515 10 5 0 5 10 15 0.0
x(µm)
plana fft gauss
6000 6000
4000 4000
2000 2000
0 0
2000 2000
4000 4000
6000 6000
6000 4000 2000 0 2000 4000 6000 6000 4000 2000 0 2000 4000 6000
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
Considérense dos máscaras con la misma topología pero de distinto tamaño. La observación del haz difractado en
el plano de Fourier cambia de escala, esto es mantiene la misma topología pero no su tamaño.
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
def difraccion_tamano(anchura):
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0)
u1.onda_plana(A = 1, theta = sp.pi / 2, phi = 0 * grados)
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.cuadrado(r0 = (0 * um, 0 * um), size = (anchura,anchura), angulo = 0 * grados)
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
return t1, u3
fft_pequeno, t1=difraccion_tamano(5*um)
fft_grande, t2 =difraccion_tamano(15*um)
plt.show()
5 µm desplazamiento 15 µm
6000 6000
4000 4000
2000 2000
0 0
2000 2000
4000 4000
6000 6000
6000 4000 2000 0 2000 4000 6000 6000 4000 2000 0 2000 4000 6000
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
5 µm tamano 15 µm
200 200
100 100
0 0
100 100
200 200
200 100 0 100 200 200 100 0 100 200
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
Si se considera una misma máscara pero distintas longitudes de onda de luz incidente en el plano de Fourier se
observa que la distribución de intensidad es la misma, pero no su tamaño. Este hecho queda patente en las figuras
ejemplo fruto de ejecutar el código de abajo. Si tuvieran distinta escala se verían diferente.
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Breaje
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
# Objetivo: Ejemplos de difraccion en campo lejano
#-------------------------------------
import sys
sys.path.append(’../’)
def difraccion_cuadrado(longitud_onda):
u1 = fuenteXY(x = x0, y = y0, wavelength = longitud_onda)
u1.onda_plana(A = 1, theta = sp.pi / 2, phi = 0 * grados)
t1 = mascaraXY(x = x0, y = y0, wavelength = longitud_onda)
t1.cuadrado(r0 = (0 * um, 0 * um), size = (15 * um, 15 * um), angulo = 0 * grados)
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
return u3
s1=difraccion_cuadrado(0.405*um)
s2=difraccion_cuadrado(0.6328*um)
dibujar_varios_campos(campos=(s1,s2), titulos=(r’$0.405\,\mu m$’, r’$0.6238\,\mu m$’), \
logaritmico=True, normalizar=’maximo’)
plt.show()
4000
0.405 µm 0.6238 µm
6000
3000
4000
2000
1000 2000
0 0
1000 2000
2000
4000
3000
4000 6000
4000300020001000 0 1000200030004000 6000 4000 2000 0 2000 4000 6000
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
Redes de Difracción
233
PIMCD2013-python, Publicación 1.0
15.1 Introducción
Podemos definir una red de difracción como un elemento óptico que modula de forma periódica alguna propiedad
de la luz. Normalmente existen redes de amplitud y de fase, aunque se puede modular de forma periódica cualquier
otra propiedad de la luz, tal como la polarización o la coherencia. Como la modulación se produce de forma
periódica, el análisis de cómo se comporta la red se puede realizar mediante un análisis de Fourier, de forma que
la transmitancia se puede describir matemáticamente mediante
∞ (︂ )︂
∑︁ 2𝜋𝑖𝑙𝑥
𝑡(𝑥) = 𝑎𝑙 exp .
𝑝
𝑙=−∞
Una red sinusoidal es un conjunto de franjas equiespaciadas cuya intensidad puede ser ajustada a una función
sinusoidal. La orientación de las franjas puede ser arbitraria. En la función que nos atañe su ángulo de inclinación
aparece en el argumento como angulo. Así se llama a la función __rotar__ dentro de la clase a fin de rotar
las franjas. Finalmente se implementa la intensidad de las franjas, entre una intensidad mínima amp_min y una
máxima amp_max. La modulación de la intensidad ha sido definida como
[︂ (︂ )︂]︂
𝐴𝑚𝑎𝑥 − 𝐴𝑚𝑖𝑛 2𝜋(𝑥 + 𝛿)
𝑡(𝑥) = 𝐴𝑚𝑖𝑛 + 1 + cos
2 𝑝
#Definicion de la sinusoidal
self.u = amp_min + (amp_max - amp_min) * (1 + sp.cos(2 * sp.pi * (Xrot + desfase)
import sys
sys.path.append(’../’)
from mascarasXY import * #@UnusedWildImport
def ejemplo_redSeno():
x = sp.linspace(-250 * um, 250 * um, 512)
y = sp.linspace(-250 * um, 250 * um, 512)
wavelength = 0.6238 * um
periodo = 75 * um
t = mascaraXY(x, y, wavelength)
t.red_seno(periodo= periodo, amp_min=0, amp_max=1,\
desfase=periodo / 2, angulo=0 * grados)
t.dibujar(tipo=’intensidad’, titulo=’red seno’)
if __name__ == ’__main__’:
ejemplo_redSeno()
plt.show()
red seno
1.0
200 0.9
0.8
100 0.7
0.6
y(µm)
0 0.5
0.4
100 0.3
0.2
200 0.1
Una red de Ronchi es un conjunto de franjas equiespaciadas tales que los máximos y los mínimos de intensidad
poseen la misma anchura. Para la implementación de una red de Ronchi se hará uso de la clase mascaraXY
así como de la función rendija ya vista anteriormente en la sección máscaras. Así en primer lugar tras llamar a la
clase y generar nuestro vector salida, t con ceros a fin de optimizar nuestra función se calcula el número de franjas,
numperiodos a partir de redondear al entero más próximo el cociente
𝑥𝑚𝑎𝑥 − 𝑥𝑚𝑖𝑛
𝑝
donde p es el periodo, x nuestro vector de entrada y xmax y xmin, los máximos y mínimos de x. Finalmente
sumamos 5 con la intención de tener un número de periodos suficientes y no quedarnos cortos debido al redondeo.
Con el fin de definir la anchura de la rendija se multiplica el factor de relleno por el periodo. Para definir las franjas
se llama para cada línea a la función rendija, así es preciso definir un punto de inicio para la implementación de
las franjas, xinicial siendo este el valor mínimo del vector de entrada menos el valor el puto central, x0.
Finalmente, mediante un bucle for se definen las franjas con inclinación angulo. Esto se hace superponiendo
nuestro vector de ceros, t, cuyos valores son los mínimos con los máximos que dan lugar del uso de la función
rendija.
#Numero de periodos
numperiodos = int(round((self.x.max() - self.x.min()) / periodo + 5))
#Anchura de la rendija
anchuraRendija = fillFactor * periodo;
#Generacion de la red
for i in range(-3, numperiodos + 1):
t2.rendija(xinicial + i * periodo, anchuraRendija, angulo)
t = t + t2
self.u = t.u
La representación de las franjas es (solo mostramos la función, pues el resto es igual al ejemplo anterior)
def ejemplo_redRonchi():
x = sp.linspace(-250 * um, 250 * um, 512)
y = sp.linspace(-250 * um, 250 * um, 512)
wavelength = 0.6238 * um
periodo = 40 * um
t = mascaraXY(x, y, wavelength)
t.red_ronchi(periodo=periodo, x0=20 * um, fillFactor=0.5, angulo=0 * grados)
t.dibujar(tipo=’intensidad’, titulo=’red Ronchi’)
red Ronchi
1.0
200 0.9
0.8
100 0.7
0.6
y(µm)
0 0.5
0.4
100 0.3
0.2
200 0.1
Una red binaria está constituida por un conjunto de franjas equiespaciadas entre sí una distancia determinada, en
este caso, a través de los parámetros de entrada podemos controlar la amplitud mínima (amin), máxima (max), el
desfase (desfase) y el factor de forma (fillFactor), además del ángulo de rotación (angulo)
def red_binaria(self, periodo = 40 * um, amin = 0, amax = 1, desfase = 0 * sp.pi / 2, x0 = 0, fill
#Numero de periodos
numperiodos = int(round((self.x.max() - self.x.min()) / periodo + 5))
#Anchura de la rendija
anchuraRendija = fillFactor * periodo;
#Se define un origen
xinicial = self.x.min() - x0
#Generacion de la red
for i in range(-3, numperiodos):
t2.rendija(xinicial + i * periodo + anchuraRendija, anchuraRendija, angulo
t.u = t.u + amax * t2.u * sp.exp(1.j * desfase)
t2.rendija(xinicial + i * periodo, periodo - anchuraRendija, angulo)
t.u = t.u + amin * t2.u
self.u = t.u
La representación queda:
def ejemplo_redBinaria():
x = sp.linspace(-250 * um, 250 * um, 512)
y = sp.linspace(-250 * um, 250 * um, 512)
wavelength = 0.6238 * um
t = mascaraXY(x, y, wavelength)
t.red_binaria( periodo = 40 * um, amin=.5, amax=1, desfase=1 * sp.pi / 2,\
x0=0, fillFactor=0.5)
t.dibujar(tipo=’campo’, titulo=’red Binaria’)
100 100
y(µm)
y(µm)
0 0
100 100
200 200
200 100 0 100 200 200 100 0 100 200
x(µm) x(µm)
0.500.550.600.650.700.750.800.850.900.951.00 0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4
Los coeficientes de Fourier de una red binaria (que incluye a la red de Ronchi como caso especial son
𝑎0 = 𝛼 𝑒−𝑖𝛿 − 1 + 1
(︀ )︀
𝑎𝑙 = 𝛼 𝑒−𝑖𝛿 − 1 𝑠𝑖𝑛𝑐(𝜋𝑙𝛼)
(︀ )︀
2𝜋 4𝜋
donde el desfase resulta ser 𝛿 = 𝜆 (𝑛 − 1)ℎ para el caso de transmisión y 𝛿 = 𝜆 (𝑛 − 1)ℎ para el caso de
reflexión.
Normalmente en una red de difracción convencional la luz difractada se concentra en su mayor parte en el orden
cero. Una red Blazed es un tipo particular de red de difracción que permite homogeneizar la luz difractada en los
distintos órdenes Esto se logra mediante una red con forma de dientes de sierra. Las redes de difracción trabajan
bajo determinadas condiciones de iluminación como es la longitud de onda del haz y el ángulo de incidente, que
en general se supone normal a la red. Así para implementar una red Blazed se comienza definiendo el vector de
onda. La definición de los dientes de sierra de la red se hace mediante el periodo de la red y la altura del diente, el
cociente de estas dos cantidades representa la pendiente de cada diente. La altura en cada punto de la red será por
tanto, la pendiente de los dientes por la posición x. La fase viene dado por el camino óptico como se muestra en k
* (indice - 1) * h la cual debe estar normalizada entre 0 y 2𝜋, esto se logra con remainder. Dado que
es relativa se fija un origen de referencia, el cual está especificado por el mínimo. Finalmente se devuelve la fase
def red_blazed(self, periodo = 40 * um, altura = 2 * um, indice = 1.5, angulo = 0 * grados
#Vector de onda
k = 2 * sp.pi / self.wavelength
#Inclinacion de las franjas
Xrot, Yrot = self.__rotar__(angulo)
#Calculo de la pendiente
pendiente = altura / periodo
#Calculo de la altura
h = Xrot * pendiente
red blazed
200 2.4
1.6
100
0.8
y(µm)
0 0.0
0.8
100
1.6
200 2.4
Una red bidimensional es aquella constituida por un conjunto de franjas verticales y horizontales, tales que per-
mite la transmisión de luz únicamente en dichas franjas. La implementación es sencilla basta con definir los dos
conjuntos de franjas horizontal y vertical y superponerlas. La superposición es equivalente a multiplicar las franjas
puesto que cada una es un matriz de ceros (no hay transmisión) y unos (transmisión permitida.
def red2D(self, periodo = 40.*um, amin = 0, amax = 1., desfase = 0.*sp.pi / 2, x0 = 0, fil
#Se inicializa
t1 = mascaraXY(self.x, self.y, self.wavelength);
t2 = mascaraXY(self.x, self.y, self.wavelength);
#Red horizontal
t1.red_binaria(periodo, amin, amax, desfase, x0, fillFactor, angulo)
#Red vertical
t2.red_binaria(periodo, amin, amax, desfase, x0, fillFactor, angulo + 90.*grados)
#Red binaria
self.u = t1.u * t2.u
red 2D
1.0
200 0.9
0.8
100 0.7
0.6
y(µm)
0 0.5
0.4
100 0.3
0.2
200 0.1
Como su propio nombre indica una red tipo ajedrez genera un damero con regiones de transmitancia unidad y
nula. Debido a como se ha definido la función, se pueden implementar distintos tipos de dameros con regiones de
transmitancia mayores y menores como se muestra en el ejemplo. La implementación del a función es completa-
mente análoga a la de la red bidimensional cambiando únicamente la última línea del código, en la cual en vez de
realizar el producto de las dos matrices, se realiza una función xor entre ellas. Esta función toda dos argumentos
binarios, y su resultado únicamente nulo cuando los dos argumentos adquieren simultáneamente el mismo valor
(0,0), o (1,1), de lo contrario el resultado es la unidad.
def red2D_ajedrez(self, periodo = 40 * um, amin = 0, amax = 1, desfase = 0 * sp.pi / 2,\
x0 = 0, fillFactor = 0.75, angulo = 0 * grados):
#Mismo proceder que en 2D
t1 = mascaraXY(self.x, self.y, self.wavelength);
t2 = mascaraXY(self.x, self.y, self.wavelength);
t1.red_binaria(periodo, amin, amax, desfase, x0, fillFactor, angulo)
t2.red_binaria(periodo, amin, amax, desfase, x0, fillFactor, angulo + 90.*grados)
#Actuacion de la XOR
self.u = sp.logical_xor(t1.u, t2.u)
red 1 red 2
200 200
100 100
0 0
100 100
200 200
200 100 0 100 200 200 100 0 100 200
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
Las redes de difracción también pueden tener simetría radial. Son parecidos a los axicones, pero con transmitancia
1 o 0.
def red_radial(self, r0 = (0 * um, 0 * um), periodo = 20 * um, desfase=0*um, radius=400*um
#si solamente hay un numero, es que las posiciones y radios son los mismos para am
if len(r0) == 1:
r0 = (r0[0], r0[0])
#Vector de onda
k = 2 * sp.pi / self.wavelength
x0, y0 = r0
#Distancia de la generatriz al eje del cono
r = sp.sqrt((self.X - x0) ** 2 + (self.Y - y0) ** 2)
#hago un seno y luego binarizo
t = 0.5*(1+sp.sin(2*sp.pi*(r+desfase)/periodo))
if binaria==True:
i0 = t <= 0.5; t[i0] = 0
i1 = t > 0.5; t[i1] = 1
#Region de transmitancia
u = sp.zeros(sp.shape(self.X))
ipasa = r < radius
u[ipasa] = 1
self.u = u * t
t1 = mascaraXY(x, y, wavelength)
t1.red_radial(r0 = (0 * um, 0 * um), periodo=20*um, desfase=10*um, radius = 100 * um)
t1.dibujar(tipo = ’intensidad’)
1.0
100 0.9
0.8
50 0.7
0.6
y(µm)
0 0.5
0.4
50 0.3
0.2
100 0.1
Las redes de difracción también pueden tener simetría angular. El periodo va en grados.
def red_angular(self, r0 = (0 * um, 0 * um), periodo = 20*grados, desfase=0*grados, radius
#si solamente hay un numero, es que las posiciones y radios son los mismos para am
if len(r0) == 1:
r0 = (r0[0], r0[0])
#Vector de onda
k = 2 * sp.pi / self.wavelength
x0, y0 = r0
#Distancia de la generatriz al eje del cono
r = sp.sqrt((self.X - x0) ** 2 + (self.Y - y0) ** 2)
theta=sp.arctan((self.Y - y0)/(self.X - x0))
#Region de transmitancia
t = (1+sp.sin(2*sp.pi*(theta-desfase)/periodo))/2
if binaria==True:
i0 = t <= 0.5; t[i0] = 0
i1 = t > 0.5; t[i1] = 1
u = sp.zeros(sp.shape(self.X))
ipasa = r < radius
u[ipasa] = 1
self.u = u * t
1.0
100 0.9
0.8
50 0.7
0.6
y(µm)
0 0.5
0.4
50 0.3
0.2
100 0.1
Este tipo de redes puede tener incidencia para eliminar armónicos en la dirección x
def red_borde_sinusoidal(self, r0 = (0 * um, 0 * um), periodo = 20*grados, lp=10*um, ap=2*
#si solamente hay un numero, es que las posiciones y radios son los mismos para am
#lp es la longitud del periodo del borde, ap es la amplitud del periodo del borde
if len(r0) == 1:
r0 = (r0[0], r0[0])
#Vector de onda
k = 2 * sp.pi / self.wavelength
x0, y0 = r0
#Distancia de la generatriz al eje del cono
r = sp.sqrt((self.X - x0) ** 2 + (self.Y - y0) ** 2)
theta=sp.arctan((self.Y - y0)/(self.X - x0))
#Region de transmitancia
Desfase=desfase+ap*sp.sin(2*sp.pi*self.Y/lp)
t = (1+sp.sin(2*sp.pi*(self.X-Desfase)/periodo))/2
if binaria==True:
i0 = t <= 0.5; t[i0] = 0
i1 = t > 0.5; t[i1] = 1
u = sp.zeros(sp.shape(self.X))
ipasa = r < radius
u[ipasa] = 1
self.u = u * t
1.0
100 0.9
0.8
50 0.7
0.6
y(µm)
0 0.5
0.4
50 0.3
0.2
100 0.1
#rotacion de la lente
Xrot, Yrot = self.__rotar__(angulo)
u = sp.zeros(sp.shape(self.X))
ipasa = r < radius
u[ipasa] = 1
self.u = u * t
t1 = mascaraXY(x, y, wavelength)
t1.red_hiperbolica( r0 = (0 * um, 0 * um), periodo = 10*um, desfase=45*grados, radius=200*
t1.dibujar(tipo=’intensidad’)
100 1.0
0.9
0.8
50
0.7
0.6
y(µm)
0 0.5
0.4
0.3
50
0.2
0.1
100100 50 0 50 100 0.0
x(µm)
El efecto de las interferencias surge directamente de que las ecuaciones de Maxwell son lineales en los campos
eléctricos y magnéticos. Supongamos que E1 y E2 son soluciones. Entonces
𝜋𝑎2
𝑧≫
𝜆
donde a es el tamaño máximo del elemento difractor. Bajo esta aproximación, el campo difractado se puede
calcular como una integral
𝑥2 +𝑦 2
𝑒𝑖𝑘(𝑧+ 2𝑧 ) ∫︁ ∫︁
𝑘
E(𝑥, 𝑦, 𝑧) = E0 (𝜉, 𝜂)𝑒−𝑖 𝑧 (𝑥𝜉+𝑦𝜂) 𝑑𝜉𝑑𝜂.
𝑖𝜆𝑧
La gran ventaja de esta ecuación es que se puede escribir como una transformada de Fourier, que es una operación
bastante conocida en matemáticas
𝑥2 +𝑦 2
𝑒𝑖𝑘(𝑧+ 2𝑧 )
E(𝑥, 𝑦, 𝑧) = 𝑇 𝐹 [E0 (𝜉, 𝜂)] .
𝑖𝜆𝑧
Como el primer término delante de la transformada de Fourier es casi constante para un plano z determinado, se
suele eliminar.
La red sinusoidal tiene solamente tres frecuencias espaciales, que son las que se observan en la figura de difracción.
def difraccion_red_seno():
#tamano de area de visualizacion
tamano = 1000 * um
ndatos = 512
x0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
y0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
#longitud de onda
lambda0 = 0.6238 * um
#fuente de iluminacion
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0);
u1.onda_plana(A = 1, theta = 0 * grados, phi = 0 * grados)
#mascara
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.red_seno(periodo=40*um)
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
u2.dibujar()
punto1=(u3.x.min(),0)
punto2=(u3.x.max(),0)
1.0
400 0.9
0.8
200 0.7
0.6
y(µm)
0 0.5
0.4
200 0.3
0.2
400 0.1
0.8
0.6
Intensidad
0.4
0.2
0.0
La red de Ronchi tiene más frecuencias espaciales, todas equidistanciadas, que son las que se observan en la figura
2
de difracción. La altura de cada pico es el cuadrado del valor del coeficiente de Fourier 𝐼𝑝𝑖𝑐𝑜,𝑛 = |𝑎𝑛 | .
def difraccion_red_ronchi():
#tamano de area de visualizacio
tamano = 1000 * um
ndatos = 512
x0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
y0 = sp.linspace(-tamano / 2, tamano / 2, ndatos)
#longitud de onda
lambda0 = 0.6238 * um
#fuente de iluminacion
u1 = fuenteXY(x = x0, y = y0, wavelength = lambda0);
u1.onda_plana(A = 1, theta = 0 * grados, phi = 0 * grados)
#mascara
t1 = mascaraXY(x = x0, y = y0, wavelength = lambda0)
t1.red_ronchi( periodo = 40 * um, x0 = 0 * um, fillFactor = 0.5, angulo = 0 * grados)
u2 = u1 * t1
u3 = u2.fft(quitar0 = False)
u2.dibujar()
punto1=(u3.x.min(),0)
punto2=(u3.x.max(),0)
1.0
400 0.9
0.8
200 0.7
0.6
y(µm)
0 0.5
0.4
200 0.3
0.2
400 0.1
0.8
0.6
Intensidad
0.4
0.2
0.0
Para conocer la distribución de intensidad en campo cercano utilizaremos la aproximación de Fresnel (ver di-
fracción en campo cercano), considerando la invariancia de la red de difracción en el eje 𝜂. Entonces el campo
difractado se puede calcular a partir de
√︂ ∫︁
𝑖 𝑖𝑘𝑧 𝑘 2
E (𝑥, 𝑧) = 𝑒 E0 (𝜉)𝑒𝑖 2𝑧 (𝑥−𝜉) 𝑑𝜉
𝜆𝑧
Resolviendo la integral para la estructura periódica de una red de difracción, el campo resulta
∞ (︂ )︂ (︂ )︂
∑︁ 𝑥 𝜆
E (𝑥, 𝑧) = 𝑎𝑙 exp 2𝜋𝑖𝑙 exp 𝑖2𝜋𝑙2 2 𝑧
𝑝 2𝑝
𝑙=−∞
150
5.6
100
4.8
50 4.0
x (microns)
0 3.2
2.4
50
1.6
100
0.8
𝑧𝑇 = 2𝑝2 /𝜆.
zT=2*periodo**2/t.wavelength
X,Z=sp.meshgrid(t.x,z)
periodo=50*um
ordenes=sp.array([-3, -1, 0, 1, 3])
coefs=sp.array([.25, 0.5, 1, 0.5, .25])
red=sp.transpose([ordenes, coefs])
u2=sp.zeros(X.shape,complex)
campoxz=sp.real(u2)
return campoxz
Podemos observar el aspecto de las automágenes simplemente calculando la distribución de intensidad en campo
cercano mediante la aproximación de Rayleigh-Sommerfeld. Esto lo hemos hecho para 3 distancias significativas
en la red de Ronchi, 𝑧𝑇 , 𝑧𝑇 /2 y 𝑧𝑇 /4:
efecto talbot
zT zT/2 zT/4
100 100 100
50 50 50
0 0 0
50 50 50
100 100 100
100 50 0 50 100 100 50 0 50 100 100 50 0 50 100
Veamos ahora que ocurre cuando sobre la red de difracción se incide con un haz gaussiano. Al propagarse la luz,
los distintos órdenes de tamaño finito, empiezan a no interferir entre sí, al no estar espacialmente en la mismas
posiciones. Entonces, deja de producirse el fenómeno interferencial y tenemos los distintos ordenes aislados. Esto
ocurre en campo cercano, y no solamente cuando estamos muy lejos de la red.
Los sistemas de doble red se utilizan frecuentemente en metrología óptica, como en deflectometría moiré para
medir la topografía 3D de objetos o en sistemas Talbot, Lau o autoimágenes generalizadas para medir desplaza-
mientos con gran precisión.
Veremos ahora algunos efectos de doble red. En el desplazamiento Talbot (movimiento de dos redes cuando es
iluminado con onda plana). Se muestra una animación
200
100
100
200
Otro efecto interesante es el efecto vernier, donde aparecen franjas cuando a) el periodo de las dos redes es distinto
y b) el ángulo entre las dos redes es distinto.
Se muestra estos dos casos mostrando una imagen y una animación:
Distinto periodo:
efecto vernier
1000
500
y(µm)
500
100
200
300
400
500
0 100 200 300 400 500
Distinto ángulo:
efecto vernier
1000
500
y(µm)
500
100
200
300
400
500
0 100 200 300 400 500
Estos dos efectos permiten medir (macroscópicamente) el desplazamiento, puesto que las franjas de vernier (las
400
200
200
400
400
200
200
400
259
PIMCD2013-python, Publicación 1.0
Así mismo se define la función de procesamiento, la cual calcula el campo producto del objeto con la ilumi-
nación. Puesto que se trabaja en el plano focal y en este se obtiene la transformada de Fourier del objeto bajo
un haz colimado, se calcula la transformada de Fourier. Finalmente se aplica el filtro realizando el producto de
la transformada de Fourier por la función filtro. Finalmente, se deshace la transformada, para obtener el campo
filtrado.
def procesar(self):
#Campo inicial
campoInicial = self.fuente * self.objeto
self.planoFourier = planoFourier
self.campoFiltrado = campoFiltrado
Resta realizar una representación de los campos a fin de comprar el incidente y el filtrado, esto se lo hace la función
dibujar. Así se genera un array de imágenes donde se ubicarán el objeto, su fase, la transformada de Fourier,
el filtro de intensidad, el filtro de fase y finalmente el campo filtrado. Así se selecciona la componente del array
donde representar la imagen (objeto, fase, transformada de Fourier...). Finalmente se dibuja con plt, el resto de
comandos determinan el título, los ejes, entre otros.
#objeto intensidad
#Seleccion de la region dela figura donde se pretende representar
plt.subplot(2, 3, 1)
#Intensidad
dibujo = abs(self.objeto.u) ** 2
#Representacion
so=self.objeto
h1 = plt.imshow(dibujo, interpolation = ’bilinear’, aspect = ’auto’,
origin = ’lower’, extent = [so.x.min(), so.x.max(), so.y.min(),\
#Titulo
plt.title(’objeto intensidad’, fontsize = 18)
#Seleccion del mapa de color
h1.set_cmap("gist_heat") #RdBu
#Los ejes no se representan
plt.axis(’off’)
#objeto fase
plt.subplot(2, 3, 2)
dibujo = sp.angle(self.objeto.u)
#Representacion
h1 = plt.imshow(dibujo, interpolation = ’bilinear’, aspect = ’auto’,
origin = ’lower’, extent = [so.x.min(), so.x.max(), so.y.min(),\
#Titulo
plt.title(’objeto fase’, fontsize = 18)
#Seleccion del mapa de color
h1.set_cmap("RdBu") #RdBu
#Los ejes no se representan
plt.axis(’off’)
#filtro intensidad
plt.subplot(2, 3, 4)
#Intensidad
dibujo = abs(self.filtro.u) ** 2
#Representacion
sf=self.filtro
h1 = plt.imshow(dibujo, interpolation = ’bilinear’, aspect = ’auto’,\
origin = ’lower’, \
extent = [sf.x.min(), sf.x.max(), sf.y.min(), sf.y.max()])
#Titulo
plt.title(’filtro intensidad’, fontsize = 18)
#Seleccion del mapa de color
h1.set_cmap("gist_heat") #RdBu
#Los ejes no se representan
plt.axis(’off’)
#filtro fase
plt.subplot(2, 3, 5)
dibujo = sp.angle(self.filtro.u)
#Representacion
h1 = plt.imshow(dibujo, interpolation = ’bilinear’, aspect = ’auto’,
origin = ’lower’, extent = [sf.x.min(), sf.x.max(), sf.y.min(),\
#Titulo
plt.title(’filtro fase’, fontsize = 18)
#Seleccion del mapa de color
h1.set_cmap("RdBu") #RdBu
#Los ejes no se representan
plt.axis(’off’)
#planoFourier
plt.subplot(2, 3, 3)
dibujo = sp.log(abs(self.planoFourier.u) ** 2 + 1)
#Representacion
pf=self.planoFourier
h1 = plt.imshow(dibujo, interpolation = ’bilinear’, aspect = ’auto’,
origin = ’lower’, extent = [pf.x.min(), pf.x.max(), pf.y.min(),\
#campoFiltrado
plt.subplot(2, 3, 6)
dibujo = sp.fliplr(sp.flipud(abs(self.campoFiltrado.u) ** 2))
#Representacion
cf=self.campoFiltrado
h1 = plt.imshow(dibujo, interpolation = ’bilinear’, aspect = ’auto’,
origin = ’lower’, extent = [cf.x.min(), cf.x.max(), cf.y.min(),\
#Titulo
plt.title(’campo Filtrado’, fontsize = 18)
#Seleccion del mapa de color
h1.set_cmap("gist_heat") #RdBu
#Los ejes no se representan
plt.axis(’off’)
Un filtro pasa es aquel que atenúa las altas frecuencias y permitiendo únicamente el paso de las bajas frecuencias.
De este modo, un filtro pasa baja conserva los detalles más generales de la imagen, eliminando los pequeños
detalles. Esto se logra en el plano de Fourier diafragmando los mayores órdenes de difracción, por ser estos los
correspondientes a altas frecuencias. En las imágenes de plano Fourier y filtro intensidad se puede apreciar este
hecho. La abertura del filtro de intensidad se ubica en el plano de Fourier, atravesando el filtro las bajas frecuencias.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""Simulaciones relacionadas con la leccion de filtrado optico"""
#Tamano
tamano = 1000 * um
x0 = sp.linspace(-tamano/2 * um, tamano/2 * um, 512)
y0 = sp.linspace(-tamano/2 * um, tamano/2 * um, 512)
#Definicion de la fuente
fuente1 = fuenteXY(x=x0, y=y0)
fuente1.onda_plana(A=1, theta=0*grados, phi=0*grados)
if __name__ == ’__main__’:
test_filtroPasaBaja()
plt.show()
Una aplicación interesante del filtro pasa baja es la eliminación de ruido en una imagen. Para comprobarlo, toma-
mos la foto de lena.png y añadimos un ruido aleatorio.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""Simulaciones relacionadas con la leccion de filtrado optico"""
tamano = 1000 * um
x0 = sp.linspace(-tamano/2 * um, tamano/2 * um, 512)
y0 = sp.linspace(-tamano/2 * um, tamano/2 * um, 512)
def test_filtroEliminarRuido():
#Definicion de la fuente
fuente1 = fuenteXY(x=x0, y=y0)
fuente1.onda_plana(A=1, theta=0*grados, phi=0*grados)
#Definicion del objeto
objeto = mascaraXY(x=x0, y=y0)
objeto.imagen(nombre=’lena.png’, normalizar=True, canal=0, tamanoImagen=False, invertir=Fa
objeto.u=objeto.u+sp.rand(len(x0),len(y0))
#Definicion del filtro
filtro = mascaraXY(x=x0, y=y0)
filtro.circulo(r0=(0*um,0*um), radius=(50*um,50*um), angulo=0*grados)
#Campo filtrado, procesado de la imagen
p1=procesadoOptico(fuente1, objeto, filtro)
p1.dibujar2(idfigs=(1,0,2,3,0,4))
p1.dibujarCampoFiltrado()
test_filtroEliminarRuido()
plt.show()
Este tipo de filtro es el opuesto al filtro pasa baja, conserva las altas frecuencias y atenúa las bajas. De este modo,
los detalles más gruesos de la imagen ser pierden, pero los pequeños se mantienen. Por ejemplo un conjunto de
franjas equiespaciadas muy separadas entre sí, se atenuarían respecto de otro conjunto de franjas poco espaciadas
entre sí. La consecución de este tipo de filtro se logra tapando los órdenes más bajos de difracción en el plano de
Fourier, atravesando únicamente el filtro, los más altos. Nuevamente, véase la imagen, como el plano de Fourier el
filtro de intensidad tapa dichos órdenes, de modo que el campo filtrado muestra los pequeños detalles de la imagen
(altas frecuencias).
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""Simulaciones relacionadas con la leccion de filtrado optico"""
import sys
sys.path.append(’../’)
from procesadoOptico import *
from fuentesXY import fuenteXY
from mascarasXY import mascaraXY
#Tamano
tamano = 1000 * um
x0 = sp.linspace(-tamano/2 * um, tamano/2 * um, 512)
y0 = sp.linspace(-tamano/2 * um, tamano/2 * um, 512)
def test_filtroPasaAlta():
#Definicion de la fuente
fuente1 = fuenteXY(x=x0, y=y0)
fuente1.onda_plana(A=1, theta=0*grados, phi=0*grados)
#Definicion del Objeto
objeto = mascaraXY(x=x0, y=y0)
objeto.imagen(nombre=’lena.png’, normalizar=True, canal=0, tamanoImagen=False, invertir=Fa
#Definicion del filtro
filtro = mascaraXY(x=x0, y=y0)
filtro.circulo(r0=(0*um,0*um), radius=(50*um,50*um), angulo=0*grados)
filtro.inversaAmplitud()
#Campo filtrado, procesado de la imagen
p1=procesadoOptico(fuente1, objeto, filtro)
p1.dibujar2(idfigs=(1,0,2,3,0,4))
p1.dibujarCampoFiltrado()
test_filtroPasaAlta()
plt.show()
veamos otro ejemplo de detección de bordes, donde éstos están más definidos.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""Simulaciones relacionadas con la leccion de filtrado optico"""
tamano = 1000 * um
x0 = sp.linspace(-tamano/2 * um, tamano/2 * um, 512)
y0 = sp.linspace(-tamano/2 * um, tamano/2 * um, 512)
def test_filtroPasaAlta2():
#Definicion de la fuente
fuente1 = fuenteXY(x=x0, y=y0)
fuente1.onda_plana(A=1, theta=0*grados, phi=0*grados)
#Definicion del bjeto
objeto = mascaraXY(x=x0, y=y0)
objeto.cuadrado(r0=(0,0), size=(250*um,250*um), angulo=45*grados)
#Definicion del filtro
filtro = mascaraXY(x=x0, y=y0)
filtro.circulo(r0=(0*um,0*um), radius=(40*um,40*um), angulo=0*grados)
filtro.inversaAmplitud()
#Campo filtrado, procesamiento de la imagen
p1=procesadoOptico(fuente1, objeto, filtro)
p1.dibujar2(idfigs=(1,0,2,3,0,4))
p1.dibujarCampoFiltrado()
test_filtroPasaAlta2()
plt.show()
Los filtros pasa banda se ubican entre los filtros pasa alta y los filtros pasa baja, permitiendo únicamente el paso de
frecuencias dentro de una banda espectral, filtrando las altas y las bajas frecuencias. Para ello, se bloquean en el
plano de Fourier los bajos y altos órdenes de difracción, así el filtro de intensidad tendrá forma de corona circular,
siendo los puntos interiores a la corona, aquellos que permiten en paso de luz.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/10/24 (version 1.0)
# Licencia: GPL
#-------------------------------------
"""Simulaciones relacionadas con la leccion de filtrado optico"""
tamano = 1000 * um
x0 = sp.linspace(-tamano/2 * um, tamano/2 * um, 512)
y0 = sp.linspace(-tamano/2 * um, tamano/2 * um, 512)
test_filtroPasaBanda()
plt.show()
Un filtro de contraste de fase potencia las diferencias de fase entre las distintas regiones del objeto. Para ello se
ilumina el objeto con luz colimada y se introduce una lámina/anillo desfasador posterior al sistema de iluminación,
el objetivo de este anillo es retardar la fase de la luz.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------
# Autor: Luis Miguel Sanchez Brea
# Fecha 2013/12/10 (version 1.5) - dibujar 2
# Licencia: GPL
#-------------------------------------
"""Simulaciones relacionadas con la leccion de filtrado optico"""
def filtroContrasteFase():
#Definicion de la fuente
fuente1 = fuenteXY(x=x0, y=y0)
fuente1.onda_plana(A=1, theta=0*grados, phi=0*grados)
#Definicion del objeto
objeto = mascaraXY(x=x0, y=y0)
objeto.imagen(nombre=’lena.png’, normalizar=True, canal=0, tamanoImagen=False, invertir=Fa
objeto.setFase(q=1, fase_min=0, fase_max=sp.pi)
#Definicion del filtro
filtro = mascaraXY(x=x0, y=y0)
filtro.circulo(r0=(0*um,0*um), radius=(4*um,4*um), angulo=0*grados)
filtro.setFase(q=1, fase_min=0, fase_max=sp.pi)
#Campo filtrado y procesado de la imagen
p1=procesadoOptico(fuente1, objeto, filtro)
p1.dibujar2(idfigs=(0,1,2,0,3,4))
p1.dibujarCampoFiltrado()
filtroContrasteFase()
plt.show()