forked from jon85p/pyENL
-
Notifications
You must be signed in to change notification settings - Fork 0
/
utils.py
478 lines (398 loc) · 15.9 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
#/usr/bin/env python3
import random
import copy
import os
import sys
pyENL_path = os.path.realpath(__file__)[0:-8]
sys.path.append(pyENL_path)
'''
Utilidades generales para uso de pyENL
'''
from numpy import *
from pyENL_fcns import *
import re
ln = log
prop = log # Solo importa el nombre de la función para comprobación...
haprop = log
from tarjan import tarjan
class configFile:
'''
Clase que facilita datos de configuración, su lectura y escritura
'''
def __init__(self, filename):
'''
Inicializada con el nombre de archivo que contiene la configuración
del programa
'''
# Primero verifica que exista el archivo, si no; carga configuración por
# defecto.
# TODO : Mejorar aspectos cuando está dañado o no existe config.txt
# items a configurar
self.items = ["lang", "method"]
self.lang = 'en'
self.method = 'hybr'
self.format = '{:,.3}'
self.tol = 1e-4
self.timeout = 10
self.sFontUI = 'Monospace,12,-1,5,25,0,0,0,0,0'
self.cuDir = os.path.expanduser('~')
try:
f = open(filename, 'rb')
texto_config = f.read().decode("utf-8").splitlines()
f.close()
for i, elm in enumerate(texto_config):
valor = elm.split("=")[1]#.replace(" ", "")
if 'lang' in elm:
self.lang = valor
if 'method' in elm:
self.method = valor
if 'format' in elm:
self.format = valor
if 'tol' in elm:
self.tol = float(valor)
if 'timeout' in elm:
self.timeout = float(valor)
if 'font' in elm:
self.sFontUI = valor
if 'cuDir' in elm:
if os.path.exists(valor): #se confirma que la ruta si existe, si no se deja la de usuario
self.cuDir = valor
except:
# Guardar con la configuración!
pass
def ajustes(texto):
'''
Ajusta el contenido del texto de cada ecuación por si se necesitan hacer
arreglos, tales como soporte para números en notación científica con
exponentes negativos
'''
pos_e_menos = buscainds(texto, 'e-')
for posicion in pos_e_menos:
# Verificar que lo que hay antes y después del e- son números.
t1 = texto[posicion - 1]
t2 = texto[posicion + 2]
try:
int(t1) # Comprueba que son números
int(t2) # Si no, entonces es excepción y chao
texto = texto.replace(t1 + 'e-' + t2, t1 + 'e ' + t2)
except:
pass
texto = texto.replace('e ', 'e')
return texto
def esalfanum(caracter):
'''Verifica que el caracter es alfanumérico o punto'''
suma = 0
suma += (caracter >= '\x30' and caracter <= '\x39')
suma += (caracter >= '\x41' and caracter <= '\x5a')
suma += (caracter >= '\x61' and caracter <= '\x7a')
es_punto = (caracter == '.' or caracter == '_')
return bool(suma + es_punto)
def buscainds(texto, busq):
'''Busca los índices de busq dentro de texto'''
index = []
end = 0
res = 0
while res != -1:
res = texto.find(busq, end)
end = res + 1
index.append(res)
index.pop(-1)
return index
def probar(texto_var):
'''
Devuelve si texto_var es o no una variable del sistema de ecuaciones
'''
try:
eval(texto_var)
return False
except:
return True
def variables(texto_eqn):
'''
Regresa en texto las variables del string texto_eqn
'''
# Reconocer lo que hay entre paréntesis
# Cambiar todos los nombres de las funciones por números
# Buscar las posiciones de "("
# Comprobar que el término anterior es número o letra
# Retroceder hasta el símbolo anterior de operación o "(" y cortar
# Cambiar todos los paréntesis por +'s
# Separar lo que esté separado por símbolos de operación:
# + - * / **
# Cambiar todos los símbolos por +'s
# Ahora comprobar con la función probar() cada uno de los elementos separados
# por los +'s
# Ir agregando los válidos siempre que no se repitan a la lista de salida
# Return esa lista
# TODO
# Eliminar los [unit] para efectos de buscar variables
texto_eqn = '1*' + texto_eqn
texto_eqn = ajustes(texto_eqn)
# print(texto_eqn)
# Índices donde hay paréntesis open
parent_abrir = buscainds(texto_eqn, '(')
# print(parent_abrir)
cambios = [] # Lista con los cambios a realizar
for posicion in parent_abrir:
if posicion > 0:
# Esto para evitar que se devuelva en la búsqueda
pos_check = posicion - 1
if esalfanum(texto_eqn[pos_check]):
# Se comprueba que es una función
EsParada = False # ¿Finalizó el nombre de la función?
while EsParada == False:
# Mientras no sea el comienzo del nombre de la función:
pos_check -= 1
if pos_check == 0:
EsParada == True
start_check = 0
else:
EsParada = not esalfanum(texto_eqn[pos_check])
start_check = pos_check + 1
cambios.append(texto_eqn[start_check:posicion])
# Ahora se efectúan todos los cambios:
for cambio in cambios:
texto_eqn = texto_eqn.replace(cambio, '1+')
# Acá eliminar lo que hay entre corchetes []
texto_eqn = re.sub(r'\[[^)]*\]', '', texto_eqn)
# Reemplazos:
A_reemplazar = ['(', ')', '-', '*', '/', '^', ',']
for termino in A_reemplazar:
texto_eqn = texto_eqn.replace(termino, '+')
posibles = texto_eqn.split('+')
# Verificar que cada término es una variable del sistema de ecuaciones
salida = []
for variable in posibles:
if variable != '':
if probar(variable) == True:
if variable not in salida:
salida.append(variable)
return salida
def random_lim(a, b):
'''
Número aleatorio entre a y b
'''
temp = random.rand()
salida = a + (b - a) * temp
return salida
def variables_string(texto):
'''
Acomoda el texto para que puedan usarse variables tipo string en el archivo
de entrada de ecuaciones.
Las variables de texto se declaran como:
#ref# = 'R404a'
'''
# 1. Identificar las lineas de declaración de estas variables
# 2. Llevar a cabo los respectivos reemplazos.
# 3. Eliminar las líneas de declaración
# 4. Lanzar excepciones si se ven variables que no han sido declaradas
dicc = {}
to_del = []
for i, eqn in enumerate(texto):
# Asegurarse de que no elimine contenido de comentarios:
if '<<' not in eqn:
try:
lista = eqn.split('=')
if len(lista) == 2 and ('#' == lista[0].replace(' ', '')[0]):
nombre_var = lista[0]
nombre_var = nombre_var.replace(' ', '')
dicc[nombre_var] = lista[1].replace(' ', '')
to_del.append(i)
except:
pass
for i, num in enumerate(to_del):
texto.pop(num-i)
# Segunda iteración para reemplazos
pattern = re.compile('|'.join(dicc.keys()))
for i, eqn in enumerate(texto):
if '<<' not in eqn:
try:
result = pattern.sub(lambda x: dicc[x.group()], eqn)
# print(eqn)
# El reemplazo:
texto[i] = result
except:
pass
# Ahora la comprobación de que no han quedado $ en las ecuaciones:
for i, eqn in enumerate(texto):
if '<<' not in eqn:
if '#' in eqn:
raise Exception('No se usa bien variable string en ', str(i))
return texto
def cantidadEqnVar(texto_caja):
'''
Regresa la cantidad de ecuaciones y de variables en el texto entero de
entrada del usuario
'''
texto_fcn = variables_string(texto_caja)
ecuaciones = 0
lista = []
for eqn in texto_fcn:
if ((eqn.replace(' ','').replace('\t', '') != '') and ('{' not in eqn)) and ('<<' not in eqn):
ecuaciones += 1
expresion = eqn.replace(" ", "")
expresion = expresion.replace("\t", "")
# Capacidad de interpretar pow
expresion = expresion.replace("^", "**")
izq_der = expresion.split('=')
paraRaiz = izq_der[0] + \
'-(' + izq_der[1] + ')' # Igualación de cero
lista.append(paraRaiz)
lista_vars = []
for ecuacion in lista:
lista_vars = lista_vars + variables(ecuacion)
lista_vars = list(set(lista_vars))
# Regresa el número de ecuaciones y de variables.
return ecuaciones, lista_vars
def actualizar_directorio(cuDir):
'''Guarda en config.txt la ultima ruta de la carpeta donde se abrió o guardó un archivo'''
# se guarda la ultima carpeta usada para cuando se vuelva a abrir el programa
f = open(pyENL_path + 'config.txt','r+')
data =f.read().splitlines() #se crea una lista con cada linea del txt
if 'cuDir' in f.read(): # si existe cuDir en el txt solo se reemplaza en la posicion "i" donde se encuentre
for i,fila in enumerate(data):
if 'cuDir' in fila:
data[i] = 'cuDir=' + str(cuDir) + '\n'
else: # de lo contrario lo crea en la ultima linea (la ultima linea está vacía)
data[-1] = 'cuDir=' + str(cuDir) + '\n'
f.seek(0) # Posiciona el cursor en el inicio
f.truncate() #Borra todo el txt para sobreescribirlo sin problemas
f.write('\n'.join(data))
f.close()
def funcion_a(Diccionario):
'''Asociación de ecuaciones con variables (opción aleatoria)'''
while True:
lista_claves = tuple(Diccionario.keys())
Diccionario_aleat = copy.deepcopy(Diccionario)
lista_claves_check = list(lista_claves)
for clave in lista_claves:
try:
# Se intenta tomar al azar un elemento (variable) de la
# ecuacion clave
variable = random.choice(Diccionario_aleat[clave])
except: # Si no se puede es porque el contenido de la clave está vacio por lo que se rompe el bucle y se comienza again
break
# Para esa clave ahora solo existira el elemento variable
Diccionario_aleat[clave] = variable
lista_claves_check.remove(clave)
for key in lista_claves_check: # Se borra la variable del resto de ecuaciones
if variable in Diccionario_aleat[key]:
Diccionario_aleat[key].remove(variable)
else: # Si se termina el primer bucle for es porque se encontró una solución
print('Eureka!!')
break
return Diccionario_aleat
def funcion_e(Diccionario):
'''Asociación de ecuaciones con variables (opción organizada)'''
Diccionario_orga = {}
lista_claves = list(Diccionario.keys())
N_ecua = len(lista_claves)
contador = 0
# Cuando las claves esta vacia será porque ya se encontró la solución
while len(Diccionario.keys()) > 0:
bandera = 0
lista = min(Diccionario.values(), key=len)
variable = lista[0]
for key in lista_claves: # Se barre todas las claves para borrar la variable del resto de Ecuaciones
if lista == Diccionario[key] and bandera == 0:
# Se almacena en el nuevo diccionario
Diccionario_orga[key] = variable
key_blue = key # Se guarda la llave para borrarla luego del diccionario
bandera = 1 # para cuando el contenido de una llave es igual al de otra
# Para borrar la variable de las demas ecuaciones
elif variable in Diccionario[key]:
Diccionario[key].remove(variable)
# se retira la clave del Dicc para no buscar ahí
Diccionario.pop(key_blue)
# ahora la lista tiene un elemento menos en el cual buscar
lista_claves = list(Diccionario.keys())
# print(key_blue,variable)
# print(Diccionario)
contador += 1
if contador > N_ecua + 1: # Seguro por si ocurre algo
raise Exception('Hubo un error D: , ni idea cual lo sentimos ')
break
return Diccionario_orga
def onevsone(matriz):
'''Asociación de ecuaciones con variables 1 a 1 (opción matricial)'''
m =matriz.copy()
size = m.shape[0]
for a in range(size):
sumCol = sum(m,axis = 0) #sumar columnas
sumRow = sum(m,axis=1) #sumar filas
minSumCol = sumCol.min()
yminSumCol = sumCol.argmin()
minSumRow = sumRow.min()
xminSumRow = sumRow.argmin()
# Revisión de un sistema de ecuaciones valido
checkCol=argwhere(sumCol== 1)
checkRow = argwhere(sumRow==1)
VcheckCol = zeros(size)
VcheckRow = zeros(size)
for b in checkCol:
VcheckCol = VcheckCol + m[:,b]
for c in checkRow:
VcheckRow =VcheckRow + m[c,:]
if True in (VcheckCol>1) or True in (VcheckCol>1):
return m ,-1
if minSumRow == 1:
# si una equ tiene solo una variable
yminSumRow =m[xminSumRow].argmax() # variable que tiene la equ
m[:,yminSumRow] = (arange(size) == xminSumRow)*(size+1)
else:
#se mira la variable que se repita el menor numero de veces
xminSumCol =m[:,yminSumCol].argmax() # equ que posee la variable
m[xminSumCol] = (arange(size) == yminSumCol)*(size+1)
m[:,yminSumCol] = (arange(size) == xminSumCol)*(size+1)
m = m//(size+1)
#Verificacion
sumCol = sum(m,axis = 0) #sumar columnas
sumRow = sum(m,axis=1) #sumar filas
ones = ones_like(sumCol)
if array_equal(sumCol,ones) and array_equal(sumRow,ones):
return m , 1
else:
return m, 0
def bloques(pyENL_eqns, pyENL_variables, tol=None, method='hybr', minEqns=3):
#def bloques(sistema_eqns, tol=None, method='hybr', minEqns=3):
'''
Recibe las ecuaciones y variables para resolver usando bloques mediante
algoritmo de Tarjan para separación de sistemas de ecuaciones
independientes entre sí.
pyENL_eqns contiene ecuaciones, pyENL_variables las variables que hay,
tol se refiere a la tolerancia del método, method es el método a usar y
minEqns es la opción para que a partir de esa cantidad de ecuaciones se
usen bloques (un valor muy bajo podría ser contraproducente ya que se
tardaría más tiempo agrupando que solucionando el sistema de un solo
llamado a la función root() )
Devuelve resultado pyENL
'''
# TODO: Optimizar el asunto de valores ya calculados para no repetición
# de cálculos de estas variables.
#1.SE CREA LA MATRIZ DEL SISTEMA DE ECUACIONES################
lista_eqn= pyENL_eqns
lista_variables=[x.name for x in pyENL_variables]
Num_eqn = len(lista_eqn)
matriz_sistema = zeros((Num_eqn,Num_eqn) , dtype = int)
for i , eqn in enumerate(lista_eqn):
varINeqn=variables(eqn) #variables funcion de utils
#Crea la fila de la matriz
fila = []
for variable in lista_variables:
if variable in varINeqn:
fila.append(1)
else:
fila.append(0)
matriz_sistema[i,:] = array(fila)
#2.CREAR MATRIZ DE RELACION 1VS1###################
Rel11, flag = onevsone(matriz_sistema)
#3.CREAR GRAFO ###################################
matrizTarjan = (matriz_sistema - Rel11).T
diccTarjan = {}
for x ,fila in enumerate(matrizTarjan):
posFila= (argwhere(fila == 1))
contenido = list(posFila.reshape(posFila.size))
diccTarjan[int(argwhere(Rel11[:,x]==1))] = contenido
bloques = tarjan(diccTarjan)
bloques.reverse()
return bloques