6  PIEP en Python

En esta sección se hará una revisión de los elementos básicos y de programación imperativa estructurada procedimental (PIEP) que requiere una implementación en Python (obviamente, Python como lenguaje de programación y NO como herramienta de cálculo o de análisis de datos).

En esta revisión se hace a una pequeña introducción a Python, a sus elementos básicos, a sus principales estructuras de control, a la creación de subprogramas, y al manejo y creación de scripts y módulos.

Se espera que al finalizar las actividades de esta sección, el estudiante entienda y tenga clara la manera en que un algoritmo, diseñado bajo el paradigma de programación imperativa estructurada procedimental, se puede implementar mediante el uso del lenguaje de programación Python.

Preparación de clase
  • Para las siguientes secciones, lea todo y ejecute todo el código que allí se incluye, haciendo todas las pruebas, cambios y experimentos que se les puedan ocurrir sobre dicho código.

En sus propias palabras, explique lo que le transmitió y lo que le enseño cada parte de lo que leyó, ejecutó, probó y experimentó; incluya su discusión, reflexiones y conclusiones al respecto; exponga lo que no entendió e intente encontrar por su cuenta respuestas a las preguntas que le surgieron, para poder compartirlas en clase.

6.1 Elementos básicos

Cuaderno computacional en Google Colaboratory:
Elementos básicos en Python

6.2 Estructuras de control

Cuaderno computacional en Google Colaboratory:
Estructuras de control en Python

6.3 Subprogramas

Cuaderno computacional en Google Colaboratory:
Creación de subprogramas en Python

6.4 Scripts y módulos

Cuaderno computacional en Google Colaboratory:
Creación y manejo de scripts y módulos en Python

6.5 Ejemplos

Ejemplo 6.1 Haga un adecuado análisis, diseño e implementación en Python de un subprograma que devuelva el valor absoluto de un número real dado. Además, implemente un programa principal que se encargue de interactuar con el usuario y que le permita probar el o los subprogramas requeridos con los valores que desee, tantas veces como lo desee.

Naturalmente, para que la programación sea modular, el programa principal y el subprograma tienen que tener tareas claramente diferenciadas. Por otra parte, el subprograma debe ser totalmente funcional por sí mismo en caso de que se desee utilizar por fuera del programa principal solicitado.

Por esta ocasión, utilizaremos isinstance() para que el subprograma tenga un comportamiento en particular cuando no recibe un número real; devolverá nan cuando el valor recibido no sea de tipo int o float.

La siguiente implementación se obtuvo, partiendo del análisis y diseño que se hizo en el Ejemplo 5.1, y usando la sintaxis de Python.

Subprograma solicitado (opción 1):

# Subprograma solicitado (opción 1)
def mi_val_abs(x):
  '''Aquí va la documentación y ayuda de la función `mi_val_abs`'''
  if isinstance(x, (int, float)):
    if x < 0:
      x = -x
  else:
    print(" ", "-"*80)
    print("  No se recibió un valor `int` o `float`, entonces se devolverá: `nan`")
    print(" ", "-"*80)
    x = float("nan")
  return x

Subprograma solicitado (opción 2):

# Subprograma solicitado (opción 2)
def mi_val_abs_v2(x):
  '''Aquí va la documentación y ayuda de la función `mi_val_abs_v2`'''
  if isinstance(x, (int, float)):
    x = -x if x < 0 else x
  else:
    print(" ", "-"*80)
    print("  No se recibió un valor `int` o `float`, entonces se devolverá: `nan`")
    print(" ", "-"*80)
    x = float("nan")
  return x

Prueba rápida de los subprogramas (sin un programa principal):

# Una prueba rápida de los subprogramas (sin un programa principal)
x = -1.234
print("  mi_val_abs(", x, ") = ", sep="")
print(mi_val_abs(x))
print("  mi_val_abs_v2(", x, ") = ", sep="")
print(mi_val_abs_v2(x))
print("  abs(", x, ") = ", sep="")
print(abs(x))
  mi_val_abs(-1.234) = 
1.234
  mi_val_abs_v2(-1.234) = 
1.234
  abs(-1.234) = 
1.234
# Otra prueba rápida de los subprogramas (sin un programa principal)
x = "Hola"
print("  mi_val_abs(", x, ") = ", sep="")
print(mi_val_abs(x))
print("  mi_val_abs_v2(", x, ") = ", sep="")
print(mi_val_abs_v2(x))
print("  abs(", x, ") = ", sep="")
print(abs(x))
  mi_val_abs(Hola) = 
  --------------------------------------------------------------------------------
  No se recibió un valor `int` o `float`, entonces se devolverá: `nan`
  --------------------------------------------------------------------------------
nan
  mi_val_abs_v2(Hola) = 
  --------------------------------------------------------------------------------
  No se recibió un valor `int` o `float`, entonces se devolverá: `nan`
  --------------------------------------------------------------------------------
nan
  abs(Hola) = 
TypeError: bad operand type for abs(): 'str'

Programa principal que permite probar y comparar el subprograma solicitado:

# Programa principal que prueba y compara el subprograma solicitado
print("Este programa prueba las funciones `mi_val_abs` y `mi_val_abs_v2`")
continuar = ""
while continuar != "No":
  x = float(input("\nPor favor, ingrese un número real:"))
  print("mi_val_abs(", x, ") = ", mi_val_abs(x), sep="")
  print("mi_val_abs_v2(", x, ") = ", mi_val_abs_v2(x), sep="")
  print("abs(", x, ") = ", abs(x), sep="")
  continuar = input("\n¿Desea continuar probando (para finalizar, ingrese la palabra: No)?")
print("Fin")

Ejemplo 6.2 Haga un adecuado análisis, diseño e implementación en Python de un subprograma que reciba un número entero, y que devuelva el valor booleano que corresponda dependiendo de si el número recibido es primo (True) o no (False). Además, implemente un programa principal que se encargue de interactuar con el usuario y que le permita probar el o los subprogramas requeridos con los valores que desee, tantas veces como lo desee.

Naturalmente, para que la programación sea modular, el programa principal y el subprograma tienen que tener tareas claramente diferenciadas. Por otra parte, el subprograma debe ser totalmente funcional por sí mismo en caso de que se desee utilizar por fuera del programa principal solicitado. El subprograma devolverá nan si recibe un número entero menor o igual a uno. Sin embargo, el subprograma no tendrá programado un comportamiento en particular para cuando recibe algo diferente a un número entero (algo diferente a un valor de tipo int).

La siguiente implementación se obtuvo: partiendo del análisis y diseño que se hizo en el Ejemplo 4.5; separando y diferenciando adecuadamente la tarea del subprograma con respecto a las del programa principal; y usando la sintaxis de Python.

# Subprograma solicitado
def mi_es_primo(k):
  '''Aquí va la documentación y ayuda de la función `mi_es_primo`'''
  if k > 1:
    res = True
    if k > 3:
      if k % 2 == 0:
        res = False
      else: 
        i = 3
        seguir = True
        while seguir:
          if k % i == 0:
            res = False
            seguir = False
          else:
            i = i + 2
            if i * i > k:
              seguir = False
  else:
    print(" ", "-"*80)
    print("  No se recibió un entero mayor que uno, entonces se devolverá: `nan`")
    print(" ", "-"*80)
    res = float("nan")
  return res

Prueba rápida del subprograma (sin un programa principal):

# Una prueba rápida de los subprogramas (sin un programa principal)
# Para poder comparar importaré la función `isprime()` del módulo `sympy`
from sympy import isprime 
k = 1234567891
print("  mi_es_primo(", k, ") = ", sep="")
print(mi_es_primo(k))
print("  isprime(", k, ") = ", sep="")
print(isprime(k))
  mi_es_primo(1234567891) = 
True
  isprime(1234567891) = 
True
# Otra prueba rápida de los subprogramas (sin un programa principal)
# Para poder comparar importaré la función `isprime()` del módulo `sympy`
from sympy import isprime 
k = -1234
print("  mi_es_primo(", k, ") = ", sep="")
print(mi_es_primo(k))
print("  isprime(", k, ") = ", sep="")
print(isprime(k))
  mi_es_primo(-1234) = 
  --------------------------------------------------------------------------------
  No se recibió un entero mayor que uno, entonces se devolverá: `nan`
  --------------------------------------------------------------------------------
nan
  isprime(-1234) = 
False

Programa principal que permite probar el subprograma solicitado:

# Programa principal que prueba el subprograma solicitado
# Para poder comparar importaré la función `isprime()` del módulo `sympy`
from sympy import isprime 
print("Este programa prueba la función `mi_es_primo`")
continuar = ""
while continuar != "No":
  k = int(input("\nPor favor, ingrese un número entero:"))
  print("  mi_es_primo(", k, ") = ", sep="")
  print(mi_es_primo(k))
  print("  isprime(", k, ") = ", sep="")
  print(isprime(k))
  continuar = input("\n¿Desea continuar probando (para finalizar, ingrese la palabra: No)?")
print("Fin")

Ejemplo 6.3 Haga un adecuado análisis, diseño e implementación en Python de un subprograma con una solución ITERATIVA y uno con una solución RECURSIVA que reciban un número entero y que devuelvan el factorial del número recibido. Además, implemente un programa principal que se encargue de interactuar con el usuario y que le permita probar el o los subprogramas requeridos con los valores que desee, tantas veces como lo desee.

Naturalmente, para que la programación sea modular, el programa principal y el subprograma tienen que tener tareas claramente diferenciadas. Por otra parte, el subprograma debe ser totalmente funcional por sí mismo en caso de que se desee utilizar por fuera del programa principal solicitado. El subprograma devolverá nan si recibe un número entero menor a cero. Sin embargo, el subprograma no tendrá programado un comportamiento en particular para cuando recibe algo diferente a un número entero (algo diferente a un valor de tipo int).

La siguiente implementación se obtuvo: partiendo del análisis y diseño que se hizo en el Ejemplo 4.4 y el Ejemplo 5.2; separando y diferenciando adecuadamente la tarea del subprograma con respecto a las del programa principal; y usando la sintaxis de Python.

Subprograma solicitado (solución iterativa):

# Subprograma solicitado (solución iterativa)
def mi_factorial_iter(k):
  '''Aquí va la documentación y ayuda de la función `mi_factorial_iter`'''
  if k >= 0:
    if k < 2:
      res = 1
    else:
      res = k
      while k > 2:
        k = k - 1
        res = res * k
  else:
    print(" ", "-"*80)
    print("  No se recibió un entero no negativo, entonces se devolverá: `nan`")
    print(" ", "-"*80)
    res = float("nan")
  return res

Subprograma solicitado (solución recursiva):

# Subprograma solicitado (solución recursiva)
def mi_factorial_recur(k):
  '''Aquí va la documentación y ayuda de la función `mi_factorial_recur`'''
  if k > 2:
    res = k * mi_factorial_recur(k - 1)
  elif k > 1:
    res = 2
  elif k >= 0:
    res = 1
  else:
    print(" ", "-"*80)
    print("  No se recibió un entero no negativo, entonces se devolverá: `nan`")
    print(" ", "-"*80)
    res = float("nan")
  return res

Prueba rápida de los subprogramas (sin un programa principal):

# Una prueba rápida de los subprogramas (sin un programa principal)
# Para poder comparar importaré la función `factorial()` del módulo `math`:
from math import factorial 
k = 6
print("  mi_factorial_iter(", k, ") = ", sep="")
print(mi_factorial_iter(k))
print("  mi_factorial_recur(", k, ") = ", sep="")
print(mi_factorial_recur(k))
print("  factorial(", k, ") = ", sep="")
print(factorial(k))
  mi_factorial_iter(6) = 
720
  mi_factorial_recur(6) = 
720
  factorial(6) = 
720
# Otra prueba rápida de los subprogramas (sin un programa principal)
# Para poder comparar importaré la función `factorial()` del módulo `math`:
from math import factorial 
k = -6
print("  mi_factorial_iter(", k, ") = ", sep="")
print(mi_factorial_iter(k))
print("  mi_factorial_recur(", k, ") = ", sep="")
print(mi_factorial_recur(k))
print("  factorial(", k, ") = ", sep="")
print(factorial(k))
  mi_factorial_iter(-6) = 
  --------------------------------------------------------------------------------
  No se recibió un entero no negativo, entonces se devolverá: `nan`
  --------------------------------------------------------------------------------
nan
  mi_factorial_recur(-6) = 
  --------------------------------------------------------------------------------
  No se recibió un entero no negativo, entonces se devolverá: `nan`
  --------------------------------------------------------------------------------
nan
  factorial(-6) = 
ValueError: factorial() not defined for negative values

Programa principal que permite probar y comparar los subprogramas solicitados:

# Programa principal que prueba los subprogramas solicitados
# Para poder comparar importaré la función `factorial()` del módulo `math`:
from math import factorial 
print("Este programa prueba las funciones `mi_factorial_iter` y `mi_factorial_recur`")
continuar = ""
while continuar != "No":
  k = int(input("\nPor favor, ingrese un número entero:"))
  print("  mi_factorial_iter(", k, ") = ", sep="")
  print(mi_factorial_iter(k))
  print("  mi_factorial_recur(", k, ") = ", sep="")
  print(mi_factorial_recur(k))
  print("  factorial(", k, ") = ", sep="")
  print(factorial(k))
  continuar = input("\n¿Desea continuar probando (para finalizar, ingrese la palabra: No)?")
print("Fin")

Ejemplo 6.4 Haga un adecuado análisis, diseño e implementación en Python de un subprograma que reciba cuatro números reales asociados a los coeficientes de un polinomio de grado 3 o inferior, y que devuelva un subprograma / una “variable” de tipo function asociado a la función matemática de los reales en los reales dada por el polinomio correspondiente a los coeficientes recibidos (es decir, se devuelve un subprograma / una “variable” de tipo function que tiene la capacidad de recibir un número real y de devolver el polinomio correspondiente evaluado en el número recibido). Además, implemente un programa principal que se encargue de interactuar con el usuario y que le permita probar el o los subprogramas requeridos con los valores que desee, tantas veces como lo desee.

Un polinomio de grado tres usualmente se escribe de la forma: \begin{aligned} p(x) &= a_3 x^3 + a_2 x^2 + a_1 x + a_0 \\ \end{aligned} Sin embargo, la anterior pueden no ser la mejor manera de hacer los cálculos, computacionalmente hablando.

Note que, \begin{aligned} a_3 x^3 + a_2 x^2 + a_1 x + a_0 = ((a_3 x + a_2) x + a_1) x + a_0 \end{aligned}

Por otra parte, si a_3 es igual a cero, entonces el polinomio p(x) no sería de grado tres, sería de grado dos; si resulta que a_2 también es igual que cero entonces el polinomio sería de grado uno: y dentro de los polinomios de grado uno tenemos cuatro posibilidades, dependiendo de si a_1 y a_0 son iguales a cero o no.

El subprograma solicitado debe tener en cuenta todo lo anterior para una adecuada implementación modular y computacionalmente eficiente. Además, se debe tener presente que el subprograma debe devolver una “variable” de tipo function, capaz de recibir un número real y de devolver el polinomio evaluado en ese número real. En este caso, el subprograma solicitado es una función de orden superior.

# Subprograma que apoya el subprograma solicitado
def devuelve_funcion_polinomio_grado1omenos(a_1, a_0):
  '''Aquí va la documentación y ayuda de la función.
  Esta función devuelve una función'''
  if a_1 != 0:
    if a_0 != 0:
      def p(x): return a_1 * x + a_0
    else:
      def p(x): return a_1 * x
  else:
    if a_0 != 0:
      def p(x): return a_0
    else:
      def p(x): return 0
  return p

# Subprograma que apoya el subprograma solicitado
def devuelve_funcion_polinomio_grado2omenos(a_2, a_1, a_0):
  '''Aquí va la documentación y ayuda de la función.
  Esta función devuelve una función'''
  if a_2 != 0:
    def p(x): 
      return (a_2 * x + a_1) * x + a_0
  else:
    p = devuelve_funcion_polinomio_grado1omenos(a_1, a_0)
  return p

# Subprograma solicitado
def devuelve_funcion_polinomio_grado3omenos(a_3, a_2, a_1, a_0):
  '''Aquí va la documentación y ayuda de la función.
  Esta función devuelve una función'''
  if a_3 != 0:
    def p(x): 
      return ((a_3 * x + a_2) * x + a_1) * x + a_0
  else:
    p = devuelve_funcion_polinomio_grado2omenos(a_2, a_1, a_0)
  return p

Prueba rápida de los subprogramas (sin un programa principal):

# Una prueba rápida de los subprogramas (sin un programa principal)
print("$p_1(x) = x^2 - 2$")
p_1 = devuelve_funcion_polinomio_grado3omenos(0, 1, 0, -2)
print("Para x:")
for i in range(-5,6,1):
  print(f'{i:5d}', end="")
print("\nUsando p_1")
for i in range(-5,6,1):
  print(f'{p_1(i):5d}', end="")
print("\nSin usar p_1")
for i in range(-5,6,1):
  print(f'{i*i-2:5d}', end="")
$p_1(x) = x^2 - 2$
Para x:
   -5   -4   -3   -2   -1    0    1    2    3    4    5
Usando p_1
   23   14    7    2   -1   -2   -1    2    7   14   23
Sin usar p_1
   23   14    7    2   -1   -2   -1    2    7   14   23
# Otra prueba rápida de los subprogramas (sin un programa principal)
print("$p_2(x) = x^3 - 2x + 2$")
p_2 = devuelve_funcion_polinomio_grado3omenos(1, 0, -2, 2)
print("Para x:")
for i in range(-5,6,1):
  print(f'{i:5d}', end="")
print("\nUsando p_1")
for i in range(-5,6,1):
  print(f'{p_2(i):5d}', end="")
print("\nSin usar p_2")
for i in range(-5,6,1):
  print(f'{i*i*i - 2*i + 2:5d}', end="")
$p_2(x) = x^3 - 2x + 2$
Para x:
   -5   -4   -3   -2   -1    0    1    2    3    4    5
Usando p_1
 -113  -54  -19   -2    3    2    1    6   23   58  117
Sin usar p_2
 -113  -54  -19   -2    3    2    1    6   23   58  117

Programa principal que permite probar y comparar el subprograma solicitado:

# Programa principal que prueba los subprogramas solicitados
print("Este programa prueba la función `devuelve_funcion_polinomio_grado3omenos`")
continuar1 = ""
while continuar1 != "No":
  a_3 = float(input("\nPor favor, ingrese un número real (a_3):"))
  a_2 = float(input("Por favor, ingrese un número real (a_2):"))
  a_1 = float(input("Por favor, ingrese un número real (a_1):"))
  a_0 = float(input("Por favor, ingrese un número real (a_0):"))
  p = devuelve_funcion_polinomio_grado3omenos(a_3, a_2, a_1, a_0)
  print("Se creo la función computacional `p` asociada al polinomio:", 
        "\np(x) = (", a_3, ")x^3 + (", a_2, ")x^2 + (", a_1, ")x + (", a_0, ")", sep="")
  continuar2 = ""
  while continuar2 != "No":
    x = float(input("\nPor favor, ingrese un número real (x):"))
    # Se usa la función computacional creada:
    print("  p(", x, ") =", sep="")
    print(p(x))
    # Para comparar:
    print("  ((a_3*x + a_2)*x + a_1)*x + a_0 =")
    print(((a_3*x + a_2)*x + a_1)*x + a_0)
    print("  a_3*(x**3) + a_2*(x**2) + a_1*x + a_0 =")
    print(a_3*(x**3) + a_2*(x**2) + a_1*x + a_0)
    continuar2 = input("\n¿Desea continuar probando el mismo polinomio (para finalizar, ingrese la palabra: No)?")
  continuar1 = input("\n¿Desea continuar probando, pero cambiando el polinomio (para finalizar, ingrese la palabra: No)?")
print("Fin")

Ejemplo 6.5 Haga un adecuado análisis, diseño e implementación en Python de un subprograma que devuelva de manera aproximada una raíz de una función de los reales en los reales dada (encontrar x \in \mathbb{R} tal que f(x) \approx 0 para f: \mathbb{R} \to \mathbb{R}), usando el método de Newton-Raphson (teniendo en cuenta todas las consideraciones del método). Además,

  • Implemente un primer programa principal que se encargue de interactuar con el usuario y que le permita probar y usar el subprograma solicitado para encontrar de manera aproximada la raíz cuadrada (positiva o negativa) de un número real positivo (c), con los valores que el usuario desee y tantas veces como lo desee.
  • Implemente un segundo programa principal que se encargue de interactuar con el usuario y que le permita probar y usar el subprograma solicitado para encontrar de manera aproximada una raíz real de un polinomio de grado 3 o inferior, con los valores que el usuario desee y tantas veces como lo desee.

La siguiente implementación del subprograma solicitado se obtuvo: partiendo del análisis y diseño en el Ejemplo 4.8, identificando adecuadamente las tareas específicas del subprograma (para que este sea totalmente funcional y útil por sí mismo sin importar si hay o no un programa principal), y usando la sintaxis de Python.

El subprograma devolverá nan en todos los casos en donde la aplicación del método no termine “exitosamente”. Sin embargo, el subprograma no tendrá programado un comportamiento en particular para cuando recibe un valor de un tipo distinto al tipo de variable esperado para cada parámetro del subprograma. Los parámetros f y df esperan recibir “variables” de tipo function de máximo un parámetro obligatorio. Por otra parte, dos de los cinco parámetros tendrán valores predeterminados, es decir, serán opcionales al momento de llamar la función: eps = 1e-12 y max_iter = 50. Se usará un subprograma propio para el cálculo de valores absolutos (mi_val_abs()), y por el momento, el subprograma informará mediante un mensaje por pantalla el motivo por el cual finalizó la aplicación del método.

Para comparar resultados, se importará la función newton() del módulo optimize del paquete scipy.

Aunque solamente se harán pruebas con los dos programas principales solicitados, el subprograma no debe depender de ellos, y por lo tanto, debe poder encontrar de manera aproximada una raíz de una función f : \mathbb{R} \to \mathbb{R} a la que se le pueda aplicar el método. Es decir, debe funcionar para cualquier subprograma de la forma:

def nombre_funcion(x):
  ...
  return y

que represente computacionalmente el accionar del objeto matemático f, cumpliendo las condiciones que requiere el método en cuestión.

# Para el subprograma solicitado utilizaré: 
# `mi_val_abs()`

# Subprograma solicitado
def mi_newton_raphson(x0, f, df, eps = 1e-12, max_iter = 50):
  xNew = x0 # Para empezar el siguiente `while` con x0 = x = xNew
  n = 0
  seguir = True
  while seguir == True and n < max_iter:
    x = xNew
    dfx = df(x)
    if mi_val_abs(dfx) < eps:
      msg = "La derivada evaluada en x_n se acerca demasiado a cero\n  "
      msg = msg + "df(x_n) = " + str(dfx)
      xNew = float("nan")
      seguir = False
    else:
      fx = f(x)
      delta = fx / dfx
      xNew = x - delta
      n = n + 1
      if mi_val_abs(fx) < eps:
        msg = "Se encontró un x para el cual |f(x)| < epsilon\n  "
        msg = msg + "f(x_n) = " + str(fx)
        seguir = False
      else:
        if mi_val_abs(delta) < eps:
          msg = "No hubo un cambio superior a epsilon de x_n a x_{n+1}\n  "
          msg = msg + "x_n - x_{n+1} = " + str(delta)
          xNew = float("nan")
          seguir = False
  if seguir == True:
    msg = "Se alcanzó el máximo de iteraciones: " + str(max_iter) + "\n  "
    msg = msg + "x_0 = " + str(x0) + ", x_n = " + str(x) + ", x_{n+1} = " + str(xNew)
    xNew = float("nan")
  print("  "+("-"*80), "Mensaje de `mi_newton_raphson()`:", msg, "-"*80, sep="\n  ")
  return xNew

En el primer programa principal se solicita utilizar el método de Newton-Raphson para encontrar de manera aproximada un x tal que x = \sqrt{c}. Es decir, una aproximación para x \in \mathbb{R} tal que, \begin{aligned} x &= \sqrt{c} \\ x^2 &= c \\ x^2 - c &= 0 \end{aligned} En otras palabras, se debe encontrar un x \in \mathbb{R} tal que f(x) \approx 0 para, f(x) = x^2 - c Como el método necesita la derivada de f(x), entonces derivando se tiene que, f'(x) = 2x Como x^2 - c es un polinomio de grado 2 y 2x es un polinomio de grado 1, se pueden usar los subprogramas devuelve_funcion_polinomio_grado2omenos() y devuelve_funcion_polinomio_grado1omenos().

Primera prueba rápida del subprograma solicitado (sin un programa principal):

# Para las pruebas rápidas y de programas principales utilizaré:
# `devuelve_funcion_polinomio_grado1omenos()`
# `devuelve_funcion_polinomio_grado2omenos()`
# `devuelve_funcion_polinomio_grado3omenos()`

# Prueba rápida del subprograma (sin un programa principal)
# Para poder comparar importaré la función `newton()` del módulo `scipy.optimize`
from scipy.optimize import newton
c = 2 # Se puede cambiar este valor por cualquier otro
for x0 in (1, -2, 0): # Se puede probar con otros valores
  print(f"\nTomando $f(x) = p(x) = x^2 - {c}$")
  print(f"y $x_0 = {x0}$")
  p = devuelve_funcion_polinomio_grado2omenos(1, 0, -c)
  dp = devuelve_funcion_polinomio_grado1omenos(2, 0)
  x = mi_newton_raphson(x0, p, dp)
  print("  La función `mi_newton_raphson()` devuelve el valor:")
  print(x)
  print("  La función `newton()` del módulo `scipy.optimize` devuelve el valor:")
  print(newton(p, x0, dp))
  print("  Se sabe que $x^2 - c = 0$ para $x = \pm \sqrt{c}$, entonces |x| =")
  print(c**0.5)

Tomando $f(x) = p(x) = x^2 - 2$
y $x_0 = 1$
  --------------------------------------------------------------------------------
  Mensaje de `mi_newton_raphson()`:
  Se encontró un x para el cual |f(x)| < epsilon
  f(x_n) = 4.440892098500626e-16
  --------------------------------------------------------------------------------
  La función `mi_newton_raphson()` devuelve el valor:
1.414213562373095
  La función `newton()` del módulo `scipy.optimize` devuelve el valor:
1.4142135623730951
  Se sabe que $x^2 - c = 0$ para $x = \pm \sqrt{c}$, entonces |x| =
1.4142135623730951

Tomando $f(x) = p(x) = x^2 - 2$
y $x_0 = -2$
  --------------------------------------------------------------------------------
  Mensaje de `mi_newton_raphson()`:
  Se encontró un x para el cual |f(x)| < epsilon
  f(x_n) = 4.440892098500626e-16
  --------------------------------------------------------------------------------
  La función `mi_newton_raphson()` devuelve el valor:
-1.414213562373095
  La función `newton()` del módulo `scipy.optimize` devuelve el valor:
-1.4142135623730951
  Se sabe que $x^2 - c = 0$ para $x = \pm \sqrt{c}$, entonces |x| =
1.4142135623730951

Tomando $f(x) = p(x) = x^2 - 2$
y $x_0 = 0$
  --------------------------------------------------------------------------------
  Mensaje de `mi_newton_raphson()`:
  La derivada evaluada en x_n se acerca demasiado a cero
  df(x_n) = 0
  --------------------------------------------------------------------------------
  La función `mi_newton_raphson()` devuelve el valor:
nan
  La función `newton()` del módulo `scipy.optimize` devuelve el valor:
RuntimeError: Derivative was zero. Failed to converge after 1 iterations, value is 0.0.

Primer programa principal que permite probar y comparar el subprograma solicitado:

# Para las pruebas rápidas y de programas principales utilizaré:
# `devuelve_funcion_polinomio_grado1omenos()`
# `devuelve_funcion_polinomio_grado2omenos()`
# `devuelve_funcion_polinomio_grado3omenos()`

# Programa principal que prueba el subprograma solicitado
# Para poder comparar importaré la función `newton()` del módulo `scipy.optimize`
from scipy.optimize import newton
print("Este programa hace una primera prueba de la función `mi_newton_raphson()`")
print("La idea aquí será encontrar de manera aproximada un x tal que f(x) = x^2 - c = 0, para un valor c dado por el usuario")
continuar1 = "Si"
while continuar1 == "Si":
  c = float(input("\nPor favor, ingrese un número real positivo (c):"))
  while c <= 0:
    c = float(input("Por favor, ingrese un número real POSITIVO (c):"))
  p = devuelve_funcion_polinomio_grado2omenos(1, 0, -c)
  dp = devuelve_funcion_polinomio_grado1omenos(2, 0)
  continuar2 = "Si"
  while continuar2 == "Si":
    x0 = float(input("\nPor favor, ingrese un número real (x0):"))
    cambiar = input("¿Desea cambiar el valor predeterminado de epsilon (para cambiarlo, ingrese la palabra: Si)? ")
    if cambiar == "Si":
      eps = float(input("Por favor, ingrese un número real positivo menor que 0.01 (epsilon):"))
      while eps <= 0 or eps >= 0.01:
        eps = float(input("Por favor, ingrese un número real POSITIVO MENOR QUE 0.01 (epsilon):"))
      x = mi_newton_raphson(x0, p, dp, eps)
    else:
      x = mi_newton_raphson(x0, p, dp)
    print("  La función `mi_newton_raphson()` devuelve el valor:")
    print(x)
    print("  La función `newton()` del módulo `scipy.optimize` devuelve el valor:")
    print(newton(p, x0, dp))
    print("  `c**0.5` es igual a:")
    print(c**0.5)
    continuar2 = input("¿Desea probar con otro valor inicial u otro epsilon (para probar con otros valores, ingrese la palabra: Si)? ")
  continuar1 = input("\n¿Desea probar con otro valor c (para probar con otro valor, ingrese la palabra: Si)? ")
print("Fin")

En el segundo programa principal, se debe encontrar un x \in \mathbb{R} tal que f(x) \approx 0 para, \begin{aligned} f(x) &= a_3 x^3 + a_2 x^2 + a_1 x + a_0 \\ &= ((a_3 x + a_2) x + a_1) x + a_0 \end{aligned} Como el método necesita la derivada de f(x), entonces derivando se tiene que, \begin{aligned} f'(x) &= 3 a_3 x^2 + 2 a_2 x + a_1 \\ &= (3 a_3 x + 2 a_2) x + a_1 \end{aligned}

Para la función objetivo y su derivada se pueden usar los subprogramas devuelve_funcion_polinomio_grado3omenos() y devuelve_funcion_polinomio_grado2omenos().

Segunda prueba rápida del subprograma solicitado (sin un programa principal):

# Para las pruebas rápidas y de programas principales utilizaré:
# `devuelve_funcion_polinomio_grado1omenos()`
# `devuelve_funcion_polinomio_grado2omenos()`
# `devuelve_funcion_polinomio_grado3omenos()`

# Prueba rápida del subprograma (sin un programa principal)
# Para poder comparar importaré la función `newton()` del módulo `scipy.optimize`
from scipy.optimize import newton
a3 = 1 # Se puede cambiar este valor por cualquier otro
a2 = 0 # Se puede cambiar este valor por cualquier otro
a1 = -2 # Se puede cambiar este valor por cualquier otro
a0 = 2 # Se puede cambiar este valor por cualquier otro
for x0 in (-2, 1): # Se puede probar con otros valores
  print(f"\nTomando $f(x) = p(x) = ({a3})x^3 + ({a2})x^2 + ({a1})x + ({a0})$")
  print(f"y $x_0 = {x0}$")
  p = devuelve_funcion_polinomio_grado3omenos(a3, a2, a1, a0)
  dp = devuelve_funcion_polinomio_grado2omenos(3*a3, 2*a2, a1)
  x = mi_newton_raphson(x0, p, dp)
  print("  La función `mi_newton_raphson()` devuelve el valor:")
  print(x)
  print("  La función `newton()` del módulo `scipy.optimize` devuelve el valor:")
  print(newton(p, x0, dp))

Tomando $f(x) = p(x) = (1)x^3 + (0)x^2 + (-2)x + (2)$
y $x_0 = -2$
  --------------------------------------------------------------------------------
  Mensaje de `mi_newton_raphson()`:
  Se encontró un x para el cual |f(x)| < epsilon
  f(x_n) = -5.053735208093713e-13
  --------------------------------------------------------------------------------
  La función `mi_newton_raphson()` devuelve el valor:
-1.7692923542386314
  La función `newton()` del módulo `scipy.optimize` devuelve el valor:
-1.7692923542386314

Tomando $f(x) = p(x) = (1)x^3 + (0)x^2 + (-2)x + (2)$
y $x_0 = 1$
  --------------------------------------------------------------------------------
  Mensaje de `mi_newton_raphson()`:
  Se alcanzó el máximo de iteraciones: 50
  x_0 = 1, x_n = 0.0, x_{n+1} = 1.0
  --------------------------------------------------------------------------------
  La función `mi_newton_raphson()` devuelve el valor:
nan
  La función `newton()` del módulo `scipy.optimize` devuelve el valor:
RuntimeError: Failed to converge after 50 iterations, value is 1.0.

Segundo programa principal que permite probar y comparar el subprograma solicitado:

# Para las pruebas rápidas y de programas principales utilizaré:
# `devuelve_funcion_polinomio_grado1omenos()`
# `devuelve_funcion_polinomio_grado2omenos()`
# `devuelve_funcion_polinomio_grado3omenos()`

# Programa principal que prueba el subprograma solicitado
# Para poder comparar importaré la función `newton()` del módulo `scipy.optimize`
from scipy.optimize import newton
print("Este programa hace una primera prueba de la función `mi_newton_raphson()`")
print("La idea aquí será encontrar una raíz real de un polinomio con coeficientes reales, de grado 3 o inferior")
print("$f(x) = p(x) = a_3 x^3 + a_2 x^2 + a_1 x + a_0$")
continuar1 = "Si"
while continuar1 == "Si":
  a3 = float(input("\nPor favor, ingrese un número real (a3):"))
  a2 = float(input("Por favor, ingrese un número real (a2):"))
  a1 = float(input("Por favor, ingrese un número real (a1):"))
  a0 = float(input("Por favor, ingrese un número real (a0):"))
  p = devuelve_funcion_polinomio_grado3omenos(a3, a2, a1, a0)
  dp = devuelve_funcion_polinomio_grado2omenos(3*a3, 2*a2, a1)
  continuar2 = "Si"
  while continuar2 == "Si":
    x0 = float(input("\nPor favor, ingrese un número real (x0):"))
    cambiar = input("¿Desea cambiar el valor predeterminado de epsilon (para cambiarlo, ingrese la palabra: Si)? ")
    if cambiar == "Si":
      eps = float(input("Por favor, ingrese un número real positivo menor que 0.01 (epsilon):"))
      while eps <= 0 or eps >= 0.01:
        eps = float(input("Por favor, ingrese un número real POSITIVO MENOR QUE 0.01 (epsilon):"))
      x = mi_newton_raphson(x0, p, dp, eps)
    else:
      x = mi_newton_raphson(x0, p, dp)
    print("  La función `mi_newton_raphson()` devuelve el valor:")
    print(x)
    print("  La función `newton()` del módulo `scipy.optimize` devuelve el valor:")
    print(newton(p, x0, dp))
    continuar2 = input("¿Desea probar con otro valor inicial u otro epsilon (para probar con otros valores, ingrese la palabra: Si)? ")
  continuar1 = input("\n¿Desea probar con otro polinomio (para probar con otro polinomio, ingrese la palabra: Si)? ")
print("Fin")

En la estimación de parámetros por máxima verosimilitud, exceptuando algunos casos, las ecuaciones de verosimilitud o log-verosimilitud, \frac{\partial }{\partial \theta} \mathrm{L}(\theta; x_1, \dots, x_n) = 0 \quad \text{ o } \quad \frac{\partial }{\partial \theta} \log \mathrm{L}(\theta; x_1, \dots, x_n) = 0, no pueden solucionarse de manera análitica. Por tanto, es necesario usar métodos numéricos o procedimientos iterativos para poder solucionar el problema de optimización asociado a dichas ecuaciones, \underset{\theta \in \Theta}{\arg \sup} \; \mathrm{L}\left(\theta; x_1, \dots, x_n \right) \quad \text{ o } \quad \underset{\theta \in \Theta}{\arg \sup} \; \log \mathrm{L}\left(\theta; x_1, \dots, x_n \right).

Existen variados métodos numéricos para resolver problemas de optimización. Buena parte de ellos toman un valor inicial para \theta, notado \hat{\theta}_0, y a partir de él buscan obtener una secuencia convergente \left\{ \hat{\theta}_k \right\}_{k=0,1,\dots}. Los algoritmos más comunmente utilizados, los Hill climbing algorithms, se basan en una ecuación de actualización de este tipo, \hat{\theta}_{k+1} = \hat{\theta}_{k} + \lambda_k \; \mathbf{\mathrm{d}}_k\left(\hat{\theta}_k\right) donde el vector \mathbf{\mathrm{d}}_k\left(\hat{\theta}_k\right) indica la “dirección del k-ésimo paso” y el escalar \lambda_n representa la “longitud o tamaño de ese k-ésimo paso”.

En el caso de los Gradient ascent algorithms, la dirección de cada paso es la del gradiente de la función objetivo, \mathbf{\mathrm{d}}_k\left(\hat{\theta}_k\right) = \Delta \left(\hat{\theta}_k\right). El gradiente de la función de log-verosimilitud con respecto al vector de parámetros suele denominarse score, \Delta \left(\theta\right) = \frac{\partial}{\partial \theta} \log \mathrm{L}.

En el método de Newton–Raphson, \lambda_k = 1 y \mathbf{\mathrm{d}}_k\left(\hat{\theta}\right) = -\mathbf{\mathrm{H}}^{-1}\left(\hat{\theta}_k\right) \Delta\left(\hat{\theta}_k\right), donde \Delta(\theta) es el score y \mathbf{\mathrm{H}}^{-1}(\theta) es el inverso de la matrix Hessiana de la función de log-verosimilitud \left( \mathbf{\mathrm{H}}(\theta) = \frac{\partial^2}{\partial \theta \partial \theta^T} \log \mathrm{L} \right), ambos evaluados en \hat{\theta}_k.

El método denominado Fisher’s Scoring es casi identico al método de Newton-Raphson, pero en vez de utilizar el negativo del inverso de la matrix Hessiana de la función de log-verosimilitud \left(-\mathbf{\mathrm{H}}^{-1}(\theta) \right), utiliza el inverso de la matrix de información de Fisher \left(\mathcal{I}^{-1}\left(\theta\right) \right). La matrix de información de Fisher es la varianza del score, \mathcal{I}\left(\theta\right) = Var \left[ \Delta\left(\theta\right) \right] = E \left[ \Delta\left(\theta\right) \Delta\left(\theta\right)^T \right] y bajo ciertas condiciones (condiciones de regularidad), también es igual al inverso aditivo del valor esperado de la matrix Hessiana de la función de log-verosimilitud, \mathcal{I}\left(\theta\right) = Var \left[ \Delta\left(\theta\right) \right] = E \left[ \Delta\left(\theta\right) \Delta\left(\theta\right)^T \right] = - E \left[ \mathbf{\mathrm{H}}(\theta) \right].

Dado que el cálculo de la matrix Hessiana o el de la matrix de información (más el cálculo de la respectiva inversa) son computacionalmente costosos, otras alternativas han sido propuestas. Una de ellas es, por ejemplo, el algoritmo de Broyden–Fletcher–Goldfarb–Shanno (BFGS).

Como si lo anterior no fuera más que suficiente:

Machine learning also has intimate ties to optimization: many learning problems are formulated as minimization of some loss function on a training set of examples. Loss functions express the discrepancy between the predictions of the model being trained and the actual problem instances (for example, in classification, one wants to assign a label to instances, and models are trained to correctly predict the pre-assigned labels of a set of examples).”

Modern neural networks model complex relationships between inputs and outputs and find patterns in data. They can learn continuous functions and even digital logical operations. Neural networks can be viewed as a type of mathematical optimization — they perform gradient descent on a multi-dimensional topology that was created by training the network. The most common training technique is the backpropagation algorithm.”

“In a recurrent neural network the signal will propagate through a layer more than once; thus, an RNN is an example of deep learning. RNNs can be trained by gradient descent, however long-term gradients which are back-propagated can”vanish” (that is, they can tend to zero) or “explode” (that is, they can tend to infinity), known as the vanishing gradient problem.”

“Many problems in AI can be solved theoretically by intelligently searching through many possible solutions… For many problems, it is possible to begin the search with some form of a guess and then refine the guess incrementally until no more refinements can be made. These algorithms can be visualized as blind hill climbing: we begin the search at a random point on the landscape, and then, by jumps or steps, we keep moving our guess uphill, until we reach the top.”

6.6 Ejercicios

Para cada uno de los siguientes puntos:

  • Implemente un programa principal que se encargue de interactuar con el usuario y que le permita probar el o los subprogramas requeridos con los valores que desee, tantas veces como lo desee.

  • Las soluciones deben ser iterativas, a menos que explicitamente se pida un solución recursiva. En aquellos casos en los que la solución debe ser iterativa, su subprograma puede hacer llamados a sí mismo, si usted así lo desea o lo requiere por alguna razón, siempre y cuando esos llamados no estén haciendo el trabajo que deberían estar haciendo estructuras iterativas.

  • El parámetro asociado a un número real positivo cercano a cero (\varepsilon) deberá tener como valor predeterminado: 1 \times 10^{-12} = 1e-12.

  • La idea es que siempre hagan sus propios subprogramas/funciones para luego comparar resultados con los que se obtienen al usar operadores o funciones ya existentes y disponibles en Python. Las funciones básicas y los operadores básicos que se referencian en Elementos Básicos son los únicos elementos ya existentes y disponibles en Python para los que no es necesario hacer una implementación propia equivalente. Como en varios ejercicios se espera una implementación propia para algún tipo de exponenciación, el operador ** o la función pow() se pueden usar pero obviamente sólo para comparar resultados. No olviden lo que se encuentra en la Introducción de la Presentación del curso y que les mencioné el primer día de clase:

    Esta NO es una asignatura para aprender a usar los paquetes, módulos o “comandos” de una herramienta informática en particular. Es un curso para entender el cómo y el porqué funcionan los elementos informáticos básicos que son usados por estadísticos / “científicos de datos” en su quehacer. En el curso, los estudiantes deben hacer sus propias implementaciones para la solución de diferentes problemas, que luego podrán comparar con las implementaciones existentes en el software especializado que deseen.

Haga un adecuado ANÁLISIS, DISEÑO e IMPLEMENTACIÓN EN PYTHON de:

  1. Un subprograma que reciba un número real (c) y que devuelva la función signo \big(\mathrm{sign}(c)\big) evaluada en el número recibido.

  2. Tres subprogramas: uno que reciba un valor asociado a grados Centigrados y que devuelva su equivalente en grados Fahrenheit, otro que reciba un valor asociado a grados Fahrenheit y que devuelva su equivalente en grados Centigrados, por último, un subprograma que reciba un número real asociado a los grados y una cadena asociada a la escala de una temperatura válida (por ejemplo, “C” para Centigrados y “F” para Fahrenheit), y que utilice los subprogramas anteriores para devolver la temperatura equivalente en la otra escala con respecto a la recibida (si recibe grados Centigrados devolverá grados Fahrenheit y viceversa).

  3. Un subprograma que reciba un número de día, un número de mes y un número de año, y que devuelva el valor booleano que corresponda, dependiendo de si la fecha asociada a esos tres datos es una fecha válida (True) o no (False) del calendario gregoriano.

  4. Un subprograma que reciba un número de día, un número de mes y un número de año de una fecha del calendario gregoriano, y que devuelva la cadena que corresponda a la fecha del día siguiente usando el formato: “año/mes/día”.

  5. Un subprograma que reciba un número entero (r), con valor predeterminado igual a cero, y que pida al usuario por pantalla, tantas veces como sea necesario, un número entero (k) mayor o igual al número entero recibido por el subprograma (k \geq r). Es decir, un subprograma que se encargue de la tarea de seguir pidiendo al usuario que ingrese un número mayor o igual a un número entero dado hasta que el usuario efectivamente lo haga.

  6. Un subprograma que reciba dos números reales (a < b), y que pida al usuario por pantalla, tantas veces como sea necesario, un número real (c) que esté entre los dos números reales recibidos por el subprograma (a < c < b). Es decir, un subprograma que se encargue de la tarea de seguir pidiendo al usuario que ingrese un número real entre dos números reales dados hasta que el usuario efectivamente lo haga.

  7. Un subprograma con una solución ITERATIVA y uno con una solución RECURSIVA que reciban dos números enteros y que devuelvan el máximo común divisor de los números recibidos. Adicionalmente, un subprograma que reciba dos números enteros y que utilice uno de los subalgoritmos anteriores (por ejemplo el iterativo) para devolver el mínimo común múltiplo de los números recibidos.

  8. Un subprograma con una solución ITERATIVA y uno con una solución RECURSIVA que reciban un número entero (k) y que devuelvan el término k-ésimo de la sucesión de los números de Fibonacci https://youtu.be/B6ztvqvZTsk.

  9. Un subprograma, con una solución ITERATIVA, que reciba un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación para el número áureo https://youtu.be/aopHcOm7a-w (\varphi) a partir del valor dado para \varepsilon. El subprograma solicitado debe tener en cuenta que:

    • \frac{F_{n+1}}{F_n} \xrightarrow[n \to \infty]{} \varphi
    • La solución debe ser computacionalmente mejor, es decir, debe tener un menor número total de operaciones, que simplemente hacer Fibonacci(n+1) / Fibonacci(n) para una función Fibonacci(k) que devuelva el término k-ésimo de la sucesión de los números de Fibonacci.
  10. Un subprograma que reciba un número real positivo cercano a cero (\varepsilon) y que devuelva una aproximación para el número \pi a partir del valor dado para \varepsilon. El subprograma solicitado debe tener en cuenta que:

    • \frac{\pi}{4} = 4 \arctan \left(\frac{1}{5}\right) - \arctan \left(\frac{1}{239}\right)
    • \sum\limits_{k = 0}^{n} \frac{(-1)^k}{2k+1}c^{2k+1} \xrightarrow[n \to \infty]{} \arctan(c)
    • La solución debe ser computacionalmente mejor, es decir, debe tener un número total de operaciones mucho menor, que simplemente hacer 4 * (4 * arctan(0.2) - arctan(1/239)) para una función arctan() que devuelva el arcotangente de un número real.
  11. Un subprograma que reciba un número real (c) y un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación del seno de c a partir del valor dado para \varepsilon. Cuando se requiera puede poner al computador a calcular, una única vez por ejecución del subprograma, una aproximación del número \pi o tomar \pi \approx 3.1415926535897932. El subprograma solicitado debe tener en cuenta que:

    • Si c < 0, entonces \sin(c) = -\sin(-c), lo que reduce el problema a calcular el seno de un valor mayor que cero (0).
    • Si c \geq 2 \pi, entonces \sin(c) = \sin(c - k \, 2 \pi), lo que reduce el problema a calcular el seno de un valor entre 0 y 2 \pi.
    • Si \pi \leq c < 2 \pi, entonces \sin(c) = -\sin(c - \pi), lo que reduce el problema a calcular el seno de un valor entre 0 y \pi.
    • Si \frac{\pi}{2} \leq c < \pi, entonces \sin(c) = \sin(\pi - c), lo que reduce el problema a calcular el seno de un valor entre 0 y \frac{\pi}{2}.
    • Si \frac{\pi}{4} \leq c < \frac{\pi}{2}, entonces \sin(c) = \cos\left(\frac{\pi}{2} - c\right), lo que reduce el problema a calcular el coseno de un valor entre 0 y \frac{\pi}{4}.
    • \sum\limits_{k = 0}^{n} \frac{(-1)^k}{(2k+1)!} c^{2k+1} \xrightarrow[n \to \infty]{} \sin(c), que se debe utilizar unicamente para calcular el seno de un valor entre 0 y \frac{\pi}{4}.
  12. Un subprograma que reciba un número real (c) y un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación del coseno de c a partir del valor dado para \varepsilon. Cuando se requiera puede poner al computador a calcular, una única vez por ejecución del subprograma, una aproximación del número \pi o tomar \pi \approx 3.1415926535897932. El subprograma solicitado debe tener en cuenta que:

    • Si c < 0, entonces \cos(c) = \cos(-c), lo que reduce el problema a calcular el coseno de un valor mayor que cero (0).
    • Si c \geq 2 \pi, entonces \cos(c) = \sin(c - k \, 2 \pi), lo que reduce el problema a calcular el coseno de un valor entre 0 y 2 \pi.
    • Si \pi \leq c < 2 \pi, entonces \cos(c) = -\cos(c - \pi), lo que reduce el problema a calcular el coseno de un valor entre 0 y \pi.
    • Si \frac{\pi}{2} \leq c < \pi, entonces \cos(c) = -\cos(\pi - c), lo que reduce el problema a calcular el coseno de un valor entre 0 y \frac{\pi}{2}.
    • Si \frac{\pi}{4} \leq c < \frac{\pi}{2}, entonces \cos(c) = \sin\left(\frac{\pi}{2} - c\right), lo que reduce el problema a calcular el seno de un valor entre 0 y \frac{\pi}{4}.
    • \sum\limits_{k = 0}^{n} \frac{(-1)^k}{(2k)!} c^{2k} \xrightarrow[n \to \infty]{} \cos(c), que se debe utilizar únicamente para calcular el coseno de un valor entre 0 y \frac{\pi}{4}.
  13. Un subprograma que reciba dos números enteros no negativos \left(k\right. y \left.r \leq k\right), y que devuelva la cantidad de reordenamientos posibles de r elementos que no se repiten tomados de un conjunto de k elementos (número de permutaciones o variaciones sin repetición, por ejemplo ver: Combinatoria - Wikipedia). El subprograma solicitado debe tener en cuenta que:

    • El número de permutaciones sin elementos repetidos es P(k, r) = k P r = \frac{k!}{(k-r)!}
    • La solución debe computacionalmente mejor, es decir, debe tener un número total de operaciones menor, que simplemente hacer Factorial(k) / Factorial(k-r) para una función Factorial() que devuelva el factorial de un número entero no negativo.
  14. Un subprograma que reciba dos números enteros no negativos \left(k\right. y \left.r \leq k\right), y que devuelva la cantidad de subconjuntos posibles de r elementos tomados de un conjunto de k elementos (número de combinaciones sin repetición, por ejemplo ver: Combinatoria - Wikipedia). El subprograma solicitado debe tener en cuenta que:

    • El número de combinaciones (coeficiente binomial) es C(k, r) = k C r = \binom{k}{r} = \frac{k!}{r! (k-r)!}
    • La solución debe ser computacionalmente mejor, es decir, debe tener un número total de operaciones menor, que simplemente hacer Factorial(k) / ( Factorial(r) * Factorial(k-r) ) o Permutacion(k,r) / Factorial(r) para una función Factorial() que devuelva el factorial de un número entero no negativo y una función Permutacion(,) que devuelva el número de permutaciones.
  15. Un subprograma con una solución ITERATIVA y uno con una solución RECURSIVA que reciban un número real (c) y un número entero (k), y que devuelvan la potencia con exponente entero c^k.

  16. Un subprograma que reciba un número real (c) y un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación de la raíz cuadrada en los complejos de c a partir del valor dado para \varepsilon. El subprograma solicitado debe tener en cuenta que:

    • Si c < 0, entonces \sqrt{c} = 0 + \sqrt{-c} \, i \in \mathbb{C}, lo que reduce el problema a calcular la raíz cuadrada de un valor mayor que cero.
    • Si \begin{aligned} a_n &= \frac{1}{2} \left(a_{n-1} + \frac{c}{a_{n-1}}\right) \\ &= \left(\frac{1}{2}\right) a_{n-1} + \frac{(c/2)}{a_{n-1}} \end{aligned} entonces, a_n \xrightarrow[n \to \infty]{} \sqrt{c}
    • Como punto de partida o valor inicial se puede utilizar: \begin{aligned} a_1 &= \frac{1 + c}{2} \\ &= \left(\frac{1}{2}\right) + (c/2) \end{aligned}

    En qué aspectos mejora o empeora el subprograma cuando adicionalmente se tiene en cuenta que:

    • Si 0 < c < 1, entonces \sqrt{c} = \frac{1}{\sqrt{1/c}}, lo que reduce el problema a calcular la raíz cuadrada de un valor mayor que uno.
    • Si c > 1, entonces \sqrt{c} = \sqrt{\left(4^k\right) \left(d\right)} = 2^k \sqrt{d}, en donde k es un entero no negativo y d es un número real entre uno y cuatro, lo que reduce el problema a calcular la raíz cuadrada de un valor entre uno y cuatro.
  17. Tres subprogramas que reciban un número real (c) y un número real positivo cercano a cero (\varepsilon) y que devuelvan, respectivamente, una aproximación del arco tangente, del arco seno y del arco coseno de c a partir del valor dado para \varepsilon. Cuando se requiera puede poner al computador a calcular, una única vez por ejecución del subprograma, una aproximación del número \sqrt{3} o tomar \sqrt{3} \approx 1.7320508075688773 y una aproximación del número \pi o tomar \pi \approx 3.1415926535897932. El subprograma solicitado debe tener en cuenta que:

    • Si c < 0, entonces \arctan(c) = - \arctan(-c), lo que reduce el problema a calcular el arco tangente de un valor mayor que cero.
    • Si c > 1, entonces \arctan(c) = \frac{\pi}{2} - \arctan\left(\frac{1}{c}\right), lo que reduce el problema a calcular el arco tangente de un valor menor o igual que uno.
    • Si c > 2 - \sqrt{3}, entonces \arctan(c) = \frac{\pi}{6} + \arctan \left( \frac{\sqrt{3} \, c - 1}{\sqrt{3} + c} \right), lo que reduce el problema a calcular el arco tangente de un valor menor o igual que 2 - \sqrt{3}.
    • \sum_{k = 0}^{n} \frac{(-1)^k}{2k+1} c^{2k+1} \xrightarrow[n \to \infty]{} \arctan(c), que se debe utilizar unicamente para calcular el arco tangente de un valor entre 0 y 2 - \sqrt{3}.
    • \arcsin(c) = \arctan\left( \frac{c}{\sqrt{1 - c^2}} \right) (utilice su subprograma que calcula la raíz cuadrada de un número)
    • \arccos(c) = \frac{\pi}{2} - \arcsin\left( c \right)
  18. Un subprograma que reciba un número real (c) y un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación de la raíz cúbica en los reales de c a partir del valor dado para \varepsilon. El subprograma solicitado debe tener en cuenta que:

    • Si c < 0, entonces \sqrt[3]{c} = - \sqrt[3]{-c} \in \mathbb{R}, lo que reduce el problema a calcular la raíz cúbica de un valor mayor que cero.
    • Si \begin{aligned} a_n &= \frac{1}{3} \left(2 a_{n-1} + \frac{c}{a^2_{n-1}}\right) \\ &= \left(\frac{2}{3}\right) a_{n-1} + \frac{(c/3)}{a^2_{n-1}} \end{aligned} entonces, a_n \xrightarrow[n \to \infty]{} \sqrt[3]{c}
    • Como punto de partida o valor inicial se puede utilizar: \begin{aligned} a_1 &= \frac{2 + c}{3} \\ &= \left(\frac{2}{3}\right) + (c/3) \end{aligned}
  19. Un subprograma que reciba un número entero diferente de cero (k \neq 0), un número real (c) y un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación de una raíz k-ésima en los reales de c a partir del valor dado para \varepsilon. El subprograma solicitado debe tener en cuenta que:

    • Si k < 0, entonces \sqrt[k]{c} = \sqrt[(-k)]{c^{-1}} = \sqrt[(-k)]{\frac{1}{c}} lo que reduce el problema a calcular una raíz entera positiva de un valor real.
    • Para k = 1 la solución es trivial, para k = 2 utilice su subprograma que calcula la raíz cuadrada de un número.
    • Si k es impar mayor que uno y c < 0, entonces \sqrt[k]{c} = - \sqrt[k]{-c} \in \mathbb{R}, lo que reduce el problema a calcular una raíz entera positiva de un valor mayor que cero (0).
    • Si k es par mayor que dos y c < 0, entonces \sqrt[k]{c} = \sqrt[k]{-c} \in \mathbb{R}, lo que reduce el problema a calcular una raíz entera positiva de un valor mayor que cero (0).
    • Si \begin{aligned} a_n &= \frac{1}{k} \left((k-1) a_{n-1} + \frac{c}{a^{(k-1)}_{n-1}}\right) \\ &= \left(\frac{k-1}{k}\right) a_{n-1} + \frac{(c/k)}{a^{k-1}_{n-1}} \end{aligned} entonces, a_n \xrightarrow[n \to \infty]{} \sqrt[k]{c}
    • Como punto de partida o valor inicial se puede utilizar: \begin{aligned} a_1 &= \frac{(k - 1) + c}{k} \\ &= \left(\frac{k-1}{k}\right) + (c/k) \end{aligned}
  20. Un subprograma que reciba un número real (c) y un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación de la función exponencial evaluada en c, a partir del valor dado para \varepsilon. Cuando se requiera puede poner al computador a calcular, una única vez por ejecución del subprograma, una aproximación del número \mathrm{e} o tomar \mathrm{e} \approx 2.7182818284590452. El subprograma solicitado debe tener en cuenta que:

    • Si c < 0, entonces \exp(c) = \frac{1}{\exp(-c)} lo que reduce el problema a calcular la función exponencial de un valor mayor que cero.
    • Si c > 1, entonces, \exp(c) = \exp(k + d) = \exp(k) \exp(d) en donde k es un entero positivo y d es un número real entre cero y uno, lo que reduce el problema a calcular la función exponencial de un valor entero positivo y un valor entre cero y uno.
    • Si c es un entero positivo, entonces, \exp(c) = \mathrm{e}^c = \underbrace{\mathrm{e} \dots \mathrm{e}}_{c \text{ veces}} (utilice su subprograma que calcula la potencia con exponente entero de un número real)
    • \sum_{k = 0}^{n} \frac{c^{k}}{k!} \xrightarrow[n \to \infty]{} \exp(c) que se debe utilizar unicamente para calcular la función exponencial de un valor entre cero y uno.
  21. Un subprograma que reciba un número real positivo (c) y un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación del logaritmo natural de c a partir del valor dado para \varepsilon. El subprograma solicitado debe usar el subprograma del punto anterior (función exponencial) y el subprograma que implementa el método de Newton-Raphson (Ejemplo 6.5). El subprograma solicitado adicionalmente debe recibir un valor inicial y un máximo número de iteraciones para que estos le sean entregados al subprograma que implementa el método de Newton-Raphson (un usuario del subprograma solicitado debe tener la posibilidad de dar el valor inicial y el máximo número de iteraciones que desee). ¿Cuáles deberían ser el valor inicial y el máximo número de iteraciones sugeridos o propuestos por quien programa, pensando en los usuarios del subprograma solicitado que no quieran o no sepan qué valores dar para esos dos parámetros?

  22. Un subprograma que reciba un número real positivo (c) y un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación del logaritmo natural de c a partir del valor dado para \varepsilon. El subprograma solicitado debe tener en cuenta que: 2 \sum_{k = 0}^{n} \frac{1}{2k+1} \left( \frac{c-1}{c+1} \right)^{2k+1} \xrightarrow[n \to \infty]{} \ln(c)

    En qué aspectos mejora o empeora el subprograma cuando adicionalmente se tiene en cuenta que:

    • Si 0 < c < 1, entonces \ln(c) = -\ln\left(\frac{1}{c}\right), lo que reduce el problema a calcular el logaritmo natural de un valor mayor que uno.
    • Si c > 1, entonces \ln(c) = \ln\left((d) \left(2^k\right)\right) = \ln(d) + k \ln\left(2\right), en donde k un entero no negativo y d es un número real entre uno y dos, lo que reduce el problema a calcular el logaritmo natural de un valor entre uno y dos.

    Cuando se requiera puede poner al computador a calcular, una única vez por ejecución del subprograma, una aproximación del número \ln\left(2\right) o tomar \ln\left(2\right) \approx 0.6931471805599453.

  23. Un subprograma que reciba un número real positivo (c) y un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación del logaritmo natural de c a partir del valor dado para \varepsilon. El subprograma solicitado debe usar un subprograma adicional que:

    Cuando se requiera puede poner al computador a calcular, una única vez por ejecución del subprograma, una aproximación del número \ln\left(2\right) o tomar \ln\left(2\right) \approx 0.6931471805599453 y una aproximación del número \pi o tomar \pi \approx 3.1415926535897932.

  24. Un subprograma que reciba un número real no negativo (c), un número real (d) y un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación de la potencia con exponente real c^d, a partir del valor dado para \varepsilon. El subprograma solicitado debe tener en cuenta que:

    c^d = \exp \big( (d) \ln(c) \big) (utilice alguno de sus subprogramas que calculan el logaritmo natural y la función exponencial de un número)

  25. Un subprograma que reciba un número real positivo (c), un número real positivo diferente de uno (d \neq 1) y un número real positivo cercano a cero (\varepsilon), y que devuelva una aproximación del logaritmo con base positiva diferente de uno de c, a partir del valor dado para \varepsilon. El subprograma solicitado debe tener en cuenta que:

    \log_d c = \frac{\ln (c)}{\ln (d)} (utilice alguno de sus subprogramas que calculan el logaritmo natural de un número)

  26. Un subprograma que:

    • Reciba dos números reales (a < b), un número real positivo cercano a cero (\varepsilon) y la implementación / el subprograma / la “variable” de tipo function correspondiente a una función matemática definida sobre el intervalo [a,b].
    • Devuelva una aproximación de una raíz entre a y b de la función recibida (encontrar x \in [a,b] tal que f(x) \approx 0 para f : [a,b] \to \mathbb{R}), a partir del valor dado para \varepsilon, usando el método de bisección, teniendo en cuenta todas las consideraciones del método.
  27. Un subprograma que:

    • Reciba dos números reales (a < b), un número entero positivo (k > 0) (número de subintervalos) y la implementación / el subprograma / la “variable” de tipo function correspondiente a una función matemática definida sobre el intervalo [a,b] (f: [a,b] \to \mathbb{R}).
    • Devuelva una aproximación de la integral definida entre a y b de la función recibida (encontrar A \approx \int_a^b f(x) \, \mathrm{d}x), a partir del valor dado para \varepsilon, usando la regla del trapecio compuesta, teniendo en cuenta todas las consideraciones del método (por ejemplo consultar Regla del trapecio - Wikipedia. Regla del trapecio compuesta).
  28. Un subprograma que:

    • Reciba dos números reales (a < b), un número entero par positivo (k > 0) (número de subintervalos) y la implementación / el subprograma / la “variable” de tipo function correspondiente a una función matemática definida sobre el intervalo [a,b] (f: [a,b] \to \mathbb{R}).
    • Devuelva una aproximación de la integral definida entre a y b de la función recibida (encontrar A \approx \int_a^b f(x) \, dx), a partir del valor dado para \varepsilon, usando la regla o método de Simpson 1/3 compuesto, teniendo en cuenta todas las consideraciones del método (por ejemplo consultar Regla de Simpson - Wikipedia. Regla de Simpson 1/3 compuesta).
  29. Un subprograma que:

    • Reciba un número real positivo cercano a cero (\varepsilon_0) y la implementación / el subprograma / la “variable” de tipo function correspondiente a una función matemática de los reales en los reales (f: \mathbb{R} \to \mathbb{R}), derivable en todo su dominio.

    • Devuelva un subprograma / una “variable” de tipo function que:

  30. Un subprograma que:

    • Reciba un número real positivo cercano a cero (\varepsilon_0) y la implementación / el subprograma / la “variable” de tipo function correspondiente a una función matemática de los reales en los reales (f: \mathbb{R} \to \mathbb{R}), derivable en todo su dominio.

    • Devuelva un subprograma / una “variable” de tipo function que: