{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# UD05 · Notebook 8 — Sistema experto como controlador de un proceso\n", "\n", "**Objetivo**: implementar un **controlador experto** que regule la temperatura de una sala,\n", "definiendo las especificaciones de respuesta (CE d) y observando cómo influye en el comportamiento\n", "del sistema (CE e).\n", "\n", "Es **práctica**: se trabaja en clase y no se entrega ni puntúa. Es el último de la unidad y da por\n", "supuestos los notebooks anteriores, en particular el control difuso del notebook 7." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Antes de empezar\n", "\n", "Python 3.10+ con `experta`. Recuerda el **parche de compatibilidad** (`collections.Mapping`\n", "desapareció en Python 3.10):\n", "\n", "```python\n", "import collections, collections.abc\n", "if not hasattr(collections, 'Mapping'):\n", " collections.Mapping = collections.abc.Mapping\n", " collections.Iterable = collections.abc.Iterable\n", " collections.MutableMapping = collections.abc.MutableMapping\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 1 — La planta\n", "\n", "Escribe el modelo de la sala: dada una temperatura y una potencia, devuelve la temperatura del\n", "siguiente instante. Tiene inercia térmica y pierde calor hacia el exterior." ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ "def simular_planta(temp, potencia, dt=1.6, inercia=0.05, exterior=15.0):\n", " \"\"\"Modelo simple de una sala con inercia termica.\"\"\"\n", " # TU CÓDIGO\n", " ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**✏️ Respuesta**: ¿qué representa cada parámetro? ¿Qué pasaría con `inercia = 0`?\n", "\n", "*(escribe aquí)*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 2 — El controlador experto\n", "\n", "Cuatro reglas sobre el **error** (consigna − temperatura): error grande → máxima potencia, error\n", "medio → media, error pequeño → baja, error negativo → apagar.\n", "\n", "### Dos trampas que te van a costar tiempo\n", "\n", "**1. El condicional va envuelto en `P(...)`.** `experta` compara el valor de un campo de un `Fact`\n", "por **igualdad**, no lo evalúa: si escribes `Fact(error=lambda e: e > 3)`, la regla busca un hecho\n", "cuyo `error` sea literalmente esa función, y nunca lo encuentra. `P(...)` es el *constraint* de\n", "predicado de `experta`: le dice al motor «aplica esta función al valor del campo y compara el\n", "resultado con verdadero». Sin `P()`, el controlador se queda **congelado con potencia 0** para\n", "siempre, y no parece un error evidente porque no lanza ninguna excepción.\n", "\n", "**2. El motor necesita `reset()` en cada paso**, o los `DefFacts` no se recargan.\n", "\n", "Con estos parámetros, la temperatura entra en la banda 20,5-21,5 ºC hacia el paso 5 y se estabiliza\n", "en torno a **21,4 ºC**, dentro de la especificación." ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ "%pip install experta\n", "\n", "# experta (2019) rompe en Python 3.10+ porque `collections.Mapping` se eliminó.\n", "# Parche de tres líneas antes de importar (ya documentado en la UD00):\n", "import collections, collections.abc\n", "if not hasattr(collections, 'Mapping'):\n", " collections.Mapping = collections.abc.Mapping\n", " collections.Iterable = collections.abc.Iterable\n", " collections.MutableMapping = collections.abc.MutableMapping\n", "\n", "from experta import *\n", "\n", "\n", "class ControladorClima(KnowledgeEngine):\n", " def __init__(self, setpoint=21.0):\n", " super().__init__()\n", " self.setpoint = setpoint\n", " self.potencia = 0.0\n", "\n", " # TU CÓDIGO: DefFacts, la regla que lee el sensor y las cuatro reglas de potencia\n", " ...\n", "\n", " def paso(self, temp):\n", " self._temp = temp\n", " self.reset()\n", " self.run()\n", " return self.potencia\n", "\n", "\n", "ctrl = ControladorClima(setpoint=21.0)\n", "temp = 15.0\n", "for i in range(200):\n", " potencia = ctrl.paso(temp)\n", " temp = simular_planta(temp, potencia)\n", " if i % 20 == 0:\n", " print(f\"t={i:3d}: temp={temp:.2f} potencia={potencia:.2f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 3 — Mide las especificaciones de respuesta\n", "\n", "Añade el código que mida las tres cosas que definen la calidad de la respuesta." ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ "# TU CÓDIGO: mide error en regimen permanente, tiempo de asentamiento y sobreimpulso\n", "# - error: temperatura final menos setpoint\n", "# - asentamiento: en que paso entra y se queda en la banda 20,5-21,5 C\n", "# - sobreimpulso: si supera 21,5 C en algun momento, y cuanto\n", "..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**✏️ Respuesta**: rellena la tabla con **tus** números.\n", "\n", "| Especificación | Valor |\n", "|---|---|\n", "| Error en régimen permanente | |\n", "| Tiempo de asentamiento (pasos) | |\n", "| Sobreimpulso máximo | |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 4 — Añade una perturbación\n", "\n", "En el paso 100, baja la temperatura exterior a 5 ºC: alguien ha abierto una ventana." ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ "# TU CÓDIGO: repite la simulacion con la perturbacion en el paso 100\n", "..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**✏️ Respuesta**: ¿mantiene la temperatura en la banda? ¿Cuánto tarda en recuperarse?\n", "\n", "*(escribe aquí)*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 5 — Analiza la sensibilidad\n", "\n", "Cambia el umbral de la regla de potencia media (de 3 a 1,5) y repite las medidas de la Fase 3." ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ "# TU CÓDIGO\n", "..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**✏️ Respuesta**: la misma tabla, **antes y después** del cambio. ¿Qué le pasa al\n", "sobreimpulso? ¿Y al tiempo de asentamiento?\n", "\n", "| Especificación | Umbral 3 | Umbral 1,5 |\n", "|---|---|---|\n", "| Error en régimen permanente | | |\n", "| Tiempo de asentamiento | | |\n", "| Sobreimpulso | | |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 6 — Compara con un PID\n", "\n", "**✏️ Respuesta**: describe cómo respondería un PID bien sintonizado frente a este controlador\n", "experto, y **cuándo usarías cada uno**. No hace falta implementarlo.\n", "\n", "*(escribe aquí)*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Cierre\n", "\n", "**✏️ Respuesta**: ¿qué relación tiene un sistema experto con los sistemas híbridos reglas/datos y\n", "con la lógica difusa? ¿Cuándo usarías cada uno?\n", "\n", "*(escribe aquí)*\n", "\n", "## Qué tienes que tener al terminar\n", "\n", "La simulación y las explicaciones de las seis fases, en este mismo notebook.\n", "\n", "| Fase | Evidencia mínima |\n", "|---|---|\n", "| 1 | La planta simulada y qué representa cada parámetro |\n", "| 2 | El controlador experto funcionando, con las reglas que se disparan en cada tramo |\n", "| 3 | La **tabla de especificaciones**: error en régimen permanente, tiempo de asentamiento y sobreimpulso |\n", "| 4 | La respuesta ante la perturbación, y si se mantiene en la banda |\n", "| 5 | La misma tabla **antes y después** de cambiar el umbral |\n", "| 6 | La comparación razonada con un PID: cuándo usarías cada uno |\n", "\n", "Las soluciones no se publican: se corrigen y comentan en clase." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.12" } }, "nbformat": 4, "nbformat_minor": 5 }