axvg commited on
Commit
c0d78bc
verified
1 Parent(s): 44a0e55
Files changed (3) hide show
  1. README.md +12 -10
  2. main.py +256 -0
  3. requirements.txt +37 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
- ---
2
- title: PC1 BE
3
- emoji: 馃摎
4
- colorFrom: red
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
+ ---
2
+ title: AG TSP API
3
+ emoji: 馃殌
4
+ colorFrom: indigo
5
+ colorTo: blue
6
+ sdk: docker
7
+ app_file: main.py
8
+ app_port: 8000
9
+ ---
10
+
11
+ ### Mi API para la PC1 de CC0A2A
12
+ Endpoint: `/shortest-path/`
main.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel
3
+ import numpy as np
4
+ import random
5
+ from typing import List, Dict
6
+
7
+
8
+ class Point(BaseModel):
9
+ id: str
10
+ x: float
11
+ y: float
12
+
13
+
14
+ class PathRequest(BaseModel):
15
+ points: List[Point]
16
+
17
+
18
+ class BezierPoint(BaseModel):
19
+ x: float
20
+ y: float
21
+
22
+
23
+ class PathResponse(BaseModel):
24
+ path: List[str]
25
+ distance: float
26
+ bezierPoints: List[BezierPoint]
27
+
28
+ class Config:
29
+ allow_population_by_field_name = True
30
+ alias_generator = lambda field_name: field_name.replace('_', '')
31
+
32
+
33
+ class InputData(BaseModel):
34
+ data: List[float] # Lista de caracter铆sticas num茅ricas (flotantes)
35
+
36
+
37
+ app = FastAPI()
38
+
39
+
40
+ def generate_bezier_points(
41
+ path: List[str],
42
+ points_dict: Dict[str, Point],
43
+ segments: int = 50
44
+ ):
45
+ bezier_points = []
46
+ if len(path) < 3:
47
+ return bezier_points
48
+
49
+ for i in range(len(path) - 2):
50
+ p0 = points_dict[path[i]]
51
+ p1 = points_dict[path[i+1]]
52
+ p2 = points_dict[path[i+2]]
53
+
54
+ for t in np.linspace(0, 1, segments):
55
+ # B(t) = (1-t)虏P0 + 2(1-t)tP1 + t虏P2
56
+ x = round((1-t)**2 * p0.x + 2*(1-t)*t * p1.x + t**2 * p2.x, 3)
57
+ y = round((1-t)**2 * p0.y + 2*(1-t)*t * p1.y + t**2 * p2.y, 3)
58
+ bezier_points.append(BezierPoint(x=x, y=y))
59
+ return bezier_points
60
+
61
+ # ------------- algoritmo genetico -------------
62
+ # Funci贸n para generar una poblaci贸n inicial aleatoria
63
+
64
+
65
+ def generar_poblacion(num_individuos, num_ciudades):
66
+ poblacion = []
67
+ for _ in range(num_individuos):
68
+ individuo = list(range(num_ciudades))
69
+ random.shuffle(individuo)
70
+ poblacion.append(individuo)
71
+ return poblacion
72
+
73
+
74
+ def calcular_aptitud(individuo, distancias, coordenadas):
75
+ # Funci贸n para evaluar la aptitud de
76
+ # un individuo (distancia total del recorrido)
77
+ distancia_total = 0
78
+ coordenadas_iguales = all(coord == coordenadas[0] for coord in coordenadas)
79
+
80
+ if not coordenadas_iguales:
81
+ for i in range(len(individuo) - 1):
82
+ ciudad_actual = individuo[i]
83
+ siguiente_ciudad = individuo[i + 1]
84
+ distancia_total += distancias[ciudad_actual][siguiente_ciudad]
85
+
86
+ distancia_total += distancias[individuo[-1]][individuo[0]]
87
+
88
+ return distancia_total
89
+
90
+ # Funci贸n para seleccionar individuos para la reproducci贸n (torneo binario)
91
+
92
+
93
+ def seleccion_torneo(poblacion, distancias, coordenadas):
94
+ seleccionados = []
95
+ for _ in range(len(poblacion)):
96
+ torneo = random.sample(poblacion, 2)
97
+ aptitud_torneo = [
98
+ calcular_aptitud(individuo, distancias, coordenadas)
99
+ for individuo in torneo
100
+ ]
101
+ seleccionado = torneo[aptitud_torneo.index(min(aptitud_torneo))]
102
+ seleccionados.append(seleccionado)
103
+ return seleccionados
104
+
105
+ # Funci贸n para realizar el cruce de dos padres para producir un hijo
106
+
107
+
108
+ def cruzar(padre1, padre2):
109
+ punto_cruce = random.randint(0, len(padre1) - 1)
110
+ hijo = padre1[:punto_cruce] + [
111
+ gen for gen in padre2 if gen not in padre1[:punto_cruce]
112
+ ]
113
+ return hijo
114
+
115
+
116
+ # Funci贸n para aplicar mutaciones en la poblaci贸n
117
+ def mutar(individuo, probabilidad_mutacion):
118
+ if random.random() < probabilidad_mutacion:
119
+ indices = random.sample(range(len(individuo)), 2)
120
+ individuo[indices[0]], individuo[indices[1]] = (
121
+ individuo[indices[1]],
122
+ individuo[indices[0]],
123
+ )
124
+ return individuo
125
+
126
+ # Funci贸n para generar distancias aleatorias
127
+ # entre ciudades y sus coordenadas bidimensionales
128
+
129
+
130
+ def generar_distancias(num_ciudades):
131
+ distancias = [[0] * num_ciudades for _ in range(num_ciudades)]
132
+ coordenadas = [
133
+ (random.uniform(0, 100), random.uniform(0, 100))
134
+ for _ in range(num_ciudades)
135
+ ]
136
+
137
+ for i in range(num_ciudades):
138
+ for j in range(i + 1, num_ciudades):
139
+ distancias[i][j] = distancias[j][i] = (
140
+ sum((x - y) ** 2
141
+ for x, y in zip(coordenadas[i], coordenadas[j])) ** 0.5
142
+ )
143
+
144
+ return distancias, coordenadas
145
+
146
+
147
+ def algoritmo_genetico(
148
+ num_generaciones, num_ciudades,
149
+ num_individuos, probabilidad_mutacion, distancias, coordenadas):
150
+ poblacion = generar_poblacion(num_individuos, num_ciudades)
151
+ for generacion in range(num_generaciones):
152
+ poblacion = sorted(
153
+ poblacion, key=lambda x: calcular_aptitud(
154
+ x, distancias, coordenadas
155
+ )
156
+ )
157
+ mejor_individuo = poblacion[0]
158
+ mejor_distancia = calcular_aptitud(
159
+ mejor_individuo, distancias, coordenadas
160
+ )
161
+ seleccionados = seleccion_torneo(poblacion, distancias, coordenadas)
162
+ nueva_poblacion = []
163
+ for i in range(0, len(seleccionados), 2):
164
+ padre1, padre2 = seleccionados[i], seleccionados[i + 1]
165
+ hijo1 = cruzar(padre1, padre2)
166
+ hijo2 = cruzar(padre2, padre1)
167
+ hijo1 = mutar(hijo1, probabilidad_mutacion)
168
+ hijo2 = mutar(hijo2, probabilidad_mutacion)
169
+ nueva_poblacion.extend([hijo1, hijo2])
170
+ poblacion = nueva_poblacion
171
+ mejor_solucion = poblacion[0]
172
+ mejor_distancia = calcular_aptitud(mejor_solucion, distancias, coordenadas)
173
+ return mejor_solucion, mejor_distancia
174
+
175
+ # Ruta de predicci贸n
176
+
177
+
178
+ @app.post("/predict/")
179
+ async def predict(data: InputData):
180
+ print(f"Data: {data}")
181
+ try:
182
+ # Convertir la lista de entrada a un array de NumPy para la predicci贸n
183
+ input_data = np.array(data.data).reshape(
184
+ 1, -1
185
+ ) # Asumiendo que la entrada debe ser de forma (1, num_features)
186
+ num_ciudades = int(input_data[0][0])
187
+ num_individuos = int(input_data[0][1])
188
+ probabilidad_mutacion = float(input_data[0][2])
189
+ num_generaciones = int(input_data[0][3])
190
+ distancias, coordenadas = generar_distancias(num_ciudades)
191
+ mejor_solucion, mejor_distancia = algoritmo_genetico(
192
+ num_generaciones,
193
+ num_ciudades,
194
+ num_individuos,
195
+ probabilidad_mutacion,
196
+ distancias,
197
+ coordenadas
198
+ )
199
+ # print(type(mejor_solucion),mejor_solucion
200
+ respuesta = list(mejor_solucion)
201
+ print(respuesta)
202
+ prediction = respuesta
203
+ # return {"prediction": prediction.tolist()}
204
+ return {"prediction": prediction}
205
+ except Exception as e:
206
+ raise HTTPException(status_code=500, detail=str(e))
207
+
208
+
209
+ @app.post("/shortest-path/", response_model=PathResponse)
210
+ async def find_shortest_path(
211
+ request: PathRequest,
212
+ population: int = 50,
213
+ mutation_prob: float = 0.1,
214
+ generations: int = 100
215
+ ):
216
+ try:
217
+ points = request.points
218
+ num_cities = len(points)
219
+ if num_cities < 3:
220
+ raise HTTPException(
221
+ status_code=400,
222
+ detail="need at least 3 points"
223
+ )
224
+
225
+ print(
226
+ f"parametros: population={population}, mutation_prob={mutation_prob}, generations={generations}"
227
+ )
228
+
229
+ distancias = [[0] * num_cities for _ in range(num_cities)]
230
+ coordenadas = [(p.x, p.y) for p in points]
231
+ points_dict = {p.id: p for p in points}
232
+ for i in range(num_cities):
233
+ for j in range(i + 1, num_cities):
234
+ dist = ((coordenadas[i][0] - coordenadas[j][0])**2 +
235
+ (coordenadas[i][1] - coordenadas[j][1])**2)**0.5
236
+ distancias[i][j] = distancias[j][i] = dist
237
+ mejor_solucion, mejor_distancia = algoritmo_genetico(
238
+ num_generaciones=generations,
239
+ num_ciudades=num_cities,
240
+ num_individuos=population,
241
+ probabilidad_mutacion=mutation_prob,
242
+ distancias=distancias,
243
+ coordenadas=coordenadas
244
+ )
245
+ path_ids = [points[i].id for i in mejor_solucion]
246
+ bezier_points = generate_bezier_points(path_ids, points_dict)
247
+ return PathResponse(
248
+ path=path_ids,
249
+ distance=mejor_distancia,
250
+ bezierPoints=bezier_points
251
+ )
252
+ except Exception as e:
253
+ raise HTTPException(
254
+ status_code=500,
255
+ detail=str(e)
256
+ )
requirements.txt ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated-types==0.7.0
2
+ anyio==4.9.0
3
+ backend-ag @ file:///C:/Users/la/repos/github.com/axvg/uni/2025-01/CC0A2A/semanas/04/pc1/backend
4
+ certifi==2025.1.31
5
+ click==8.1.8
6
+ colorama==0.4.6
7
+ dnspython==2.7.0
8
+ email_validator==2.2.0
9
+ fastapi==0.115.12
10
+ fastapi-cli==0.0.7
11
+ h11==0.14.0
12
+ httpcore==1.0.8
13
+ httptools==0.6.4
14
+ httpx==0.28.1
15
+ idna==3.10
16
+ Jinja2==3.1.6
17
+ markdown-it-py==3.0.0
18
+ MarkupSafe==3.0.2
19
+ mdurl==0.1.2
20
+ numpy==2.2.5
21
+ pydantic==2.11.3
22
+ pydantic_core==2.33.1
23
+ Pygments==2.19.1
24
+ python-dotenv==1.1.0
25
+ python-multipart==0.0.20
26
+ PyYAML==6.0.2
27
+ rich==14.0.0
28
+ rich-toolkit==0.14.1
29
+ shellingham==1.5.4
30
+ sniffio==1.3.1
31
+ starlette==0.46.2
32
+ typer==0.15.2
33
+ typing-inspection==0.4.0
34
+ typing_extensions==4.13.2
35
+ uvicorn==0.34.2
36
+ watchfiles==1.0.5
37
+ websockets==15.0.1