#!/bin/bash
# Pruebas de d2datdinc (de .d/.ds/.ds2 al dato de Simul).
#
#   d2datdinc/probar               estado 0 si todas pasan
#
# Corre como TESTS de automake, asi que no construye nada: el binario tiene
# que estar. El -c y el -b son porque el programa compilado busca su tabla de
# regexps y su biblioteca donde se instalan, y aca todavia no estan.
#
# circuitoB.ds tiene que dar pruebas/circuitoB.esperado.dat y 6elementos.d
# pruebas/6elementos.esperado.dat. Los dos esperados salieron de ds2dat, pero
# desde que los numeros van con 17 cifras y no con las 15 que escribe Perl son
# referencias propias: el cruce contra el otro parser es pruebas/contra-ds2dat,
# que lo corre en vivo.
# sin -v, nada por stderr. Despues, tablas de un .d, DATABLOB a ExprTk,
# LINEAL, tres errores de Siru arreglados y reglas del manual de SCEPTRE. La comparacion contra todo el corpus es pruebas/contra-ds2dat.
#
# Copyright (C) Diego Saravia <dsa@unsa.edu.ar>
# GPL version 3 o posterior.

# Todo lo que hace falta esta en pruebas/: no se sale de slex. La comparacion
# contra Simulparser, que si lo necesita, es pruebas/contra-ds2dat aparte.
# Sufijo del generador, para no mezclar sus salidas con las de otros.
QUIEN=-de-d2datdinc
AQUI=$(cd "$(dirname "$0")" && pwd)
cd "$AQUI" || exit 1
S=$T
D=${D2DATDINC:-./d2datdinc}
[ -x "$D" ] || { echo "no esta $D: falta hacer make"; exit 77; }
D="$D -c $AQUI/d2datdinc.slex -b $AQUI/biblioteca"
P="$D"
T=$(mktemp -d)
trap 'rm -rf "$T"' EXIT

n=0; fallan=0
ok() {   # ok CONDICION_DE_SALIDA DESCRIPCION
  n=$((n+1))
  if [ "$1" -eq 0 ]; then echo "  ok    $2"; else echo "  FALLA $2"; fallan=$((fallan+1)); fi
}
# igual A B: el mismo dato sin OTHERINFO (tiene rutas)
igual() {
  python3 - "$1" "$2" <<'PY'
import json, sys, re
a, b = (json.load(open(f)) for f in sys.argv[1:3])
for d in (a, b): d.pop('OTHERINFO', None)
sys.exit(0 if a == b else 1)
PY
}

$D pruebas/circuitoB.ds -o "$T/b.dat" 2>"$T/b.err"
ok $? 'circuitoB.ds se convierte'
igual "$T/b.dat" pruebas/circuitoB.esperado.dat
ok $? 'circuitoB.ds da circuitoB.esperado.dat'
test ! -s "$T/b.err"
ok $? 'sin -v no escribe nada por stderr'

$D pruebas/6elementos.d -o "$T/6e.dat" 2>"$T/6e.err"
ok $? '6elementos.d se convierte'
igual "$T/6e.dat" pruebas/6elementos.esperado.dat
ok $? '6elementos.d da lo mismo que d2ds + ds2dat'

$D pruebas/tablas.d -o - 2>"$T/t.err" >"$T/t.dat"
ok $? 'tablas.d a la salida estandar'
python3 - "$T/t.dat" <<'PY'
import json, sys, re
G = json.load(open(sys.argv[1]))['GLOBAL']
# el bloque entero, como texto: los pares los lee tabular2 (#formato pares)
t = G['tablas']['EIN']
v = [float(x) for x in re.split(r'[\s,]+', t['texto']) if x]
assert t['formato'] == 'pares' and [v[i:i + 2] for i in range(0, len(v), 2)] == [[0, 0], [1, 1], [2, 0]], t
assert G['ramas']['E1']['valores'][0][0] == "Table('EIN.txt','X','Y',T,1)"
assert G['defpar']['PX']['value'] == "Table('EIN.txt','X','Y',PY,1)"
assert G['ramas']['L1']['valores'][0][0] == '2 * 3'          # linea con \
assert G['control']['RK']['value'] == [12]                     # TRAP
assert G['defpar']['TIEMPOFINAL']['value'] == '5'
PY
ok $? 'TABLE del .d en GLOBAL.tablas; TABLE X (TIME) a Table(), linea con \'

$D pruebas/datablob.ds -o "$T/db.dat" 2>/dev/null
python3 - "$T/db.dat" <<'PY'
import json, sys, re
G = json.load(open(sys.argv[1]))['GLOBAL']
assert G['perlfunctions']['numrk'] == {'tipo': 'PRE', 'exprtk': 'if ((step % 3) == 0, 11, 18)'}
assert G['DATABLOB'].startswith("$datablob->{'perlfunctions'}->{'numrk'}->{'tipo'}='PRE';")
assert G['hequations'][1]['vec'] == [-1, 1] and 'eq' not in G['hequations'][1]
PY
ok $? 'DATABLOB numrk a ExprTk; LINEAL da vec'

# DATA con la gramatica de data.y: lo que vale, y lo raro, que se avisa y no
# toca el dato
$D -v pruebas/data.ds -o "$T/data.dat" 2>"$T/data.err"
python3 - "$T/data.dat" <<'PY'
import json, sys, re
D = json.load(open(sys.argv[1]))['GLOBAL']['data']
assert D['rk'] == [None, None, {'a': [[0, 0], [0.5, 0]], 'name': 'Punto medio'}], D['rk']
assert D['cuentas'] == {'sin_flecha': 4}
assert D['lista'] == [1, 'x', None, [512, -4]]
assert abs(D['cuenta'] - (2.25 - 2 + 0 + 0.1)) < 1e-12
assert D['texto'] == "con 'comilla' adentro" and D['nada'] is None
assert 'raro' not in D
PY
ok $? 'DATA: caminos con y sin flecha, listas, cuentas, texto y undef'
test "$(grep -c 'DATA: no se entiende' "$T/data.err")" -eq 10
ok $? 'DATA: las diez sentencias raras se avisan y no entran'
grep -q 'indice va entre corchetes' "$T/data.err" && grep -q 'hexadecimal' "$T/data.err" && grep -q 'comilla sin cerrar' "$T/data.err" \
  && grep -q 'atan2 lleva dos' "$T/data.err" && grep -q 'no es un objeto' "$T/data.err" && grep -q 'no es una lista' "$T/data.err"
ok $? 'DATA: cada rareza dice cual es'

# EQUATIONS y CONTROL EQUATIONS con la gramatica de ecuacion.y: lo que vale, y
# lo raro, que se avisa y la sentencia se ignora
$D -v pruebas/ecuaciones.ds -o "$T/ecu.dat" 2>"$T/ecu.err"
python3 - "$T/ecu.dat" <<'PY'
import json, sys, re
H = json.load(open(sys.argv[1]))['GLOBAL']['hequations']
assert len(H) == 6, len(H)
assert H[0]['eq'] == 'x * x - 20' and H[0]['vars'] == ['x'] and H[0]['tipo'] == 'N' and H[0]['nucleo'] == 1
assert H[1]['eq'] == 'sin (P_T) + 2 * exp ( L * P_T)' and H[1]['nucleo'] == 0 and H[1]['res'] == 'SY3' and H[1]['tipo'] == 'POST'
assert H[2]['vec'] == [1, -1] and 'eq' not in H[2] and H[2]['vars'] == ['y', 'z'] and H[2]['indep'] == '2' and H[2]['lineal'] == 1
assert H[3]['eq'] == 'DX=-6 x +5 * y +10' and H[3]['vars'] == []
assert H[4]['eq'] == 'T<TIEMPOFINAL' and H[4]['vars'] == ['T', 'TIEMPOFINAL'] and H[4]['tipo'] == 'C' and H[4]['nucleo'] == 0
assert H[4]['linea'] == 'T<TIEMPOFINALV:T,TIEMPOFINAL'   # Siru: s/\s// (el primer blanco)
assert H[5]['eq'] == 'not(VRL2>=20)' and H[5]['vars'] == ['VRL2']
PY
ok $? 'EQUATIONS: la ecuacion, las marcas y sus valores; CONTROL EQUATIONS de tipo C'
test "$(grep -c 'no se entiende, se ignora' "$T/ecu.err")" -eq 11
ok $? 'EQUATIONS: las once sentencias raras se avisan y no entran'
for m in 'solo van sus valores' 'va una sola vez' 'lleva un valor' 'no lleva valores' 'sin variables' \
         'blanco antes de una coma' 'pegada a lo anterior' 'vector de una LINEAL'; do
  grep -q "$m" "$T/ecu.err" || { echo "falta el aviso: $m"; false; }
done
ok $? 'EQUATIONS: cada rareza dice cual es'

# CONSTANTS y FUNCTIONS con la gramatica de declaraciones.y: lo que vale, y lo
# raro, que se avisa y la sentencia se ignora
$D -v pruebas/declaraciones.ds -o "$T/decl.dat" 2>"$T/decl.err"
python3 - "$T/decl.dat" <<'PY'
import json, sys, re
G = json.load(open(sys.argv[1]))['GLOBAL']
assert G['constants'] == {'K': '1E-3', 'CALCULA_PMA': '5', 'J': '-.5'}, G['constants']
assert G['lconstants'] == ['K', 'CALCULA_PMA', 'J']
F = G['funciones']
assert F['QC'] == ['QC', 'PC * VC', 0, ['PC', 'VC']] and F['Q3'] == ['Q3', '2 * T', '5', []]
assert F['F'] == ['F', '1 + 2', 0, []]          # el primer blanco era el de antes del =
assert F['G'] == ['G', 'X+ 1', 0, ['X']]         # Siru: s/\\s// (el primer blanco)
assert sorted(F) == ['F', 'G', 'Q3', 'QC'] and G['lisfun'] == ['QC', 'Q3', 'F', 'G']
PY
ok $? 'CONSTANTS y FUNCTIONS: nombre, numero, argumentos y expresion'
test "$(grep -c 'no se entiende (se ignora\|ya esta definida' "$T/decl.err")" -eq 10
ok $? 'CONSTANTS y FUNCTIONS: las diez sentencias raras se avisan y no entran'
for m in 'ya esta definida' 'va sin _' 'blancos adentro' 'un argumento empieza con una letra'; do
  grep -q "$m" "$T/decl.err" || { echo "falta el aviso: $m"; false; }
done
ok $? 'CONSTANTS y FUNCTIONS: las rarezas con nombre dicen cual son'

# DEFINED PARAMETERS y VARIABLES con la gramatica de parametros.y
$D -v pruebas/parametros.ds -o "$T/par.dat" 2>"$T/par.err"
python3 - "$T/par.dat" <<'PY'
import json, sys, re
G = json.load(open(sys.argv[1]))['GLOBAL']
P = G['defpar']
assert P['PX'] == {'value': '0.5'} and P['X'] == {'value': '0', 'der': 'DX'}
assert P['PDSUM'] == {'value': 'X1(PDUM0+PDUM1)'} and P['P_T'] == {'value': '5'}
assert sorted(P) == ['PDSUM', 'PX', 'P_T', 'X'], sorted(P)
assert 'lisvar' not in G       # como en Siru: solo con ecuaciones
assert G['variables'] == {'DX': {'value': '-1', 'der': ''}, 'Z': {'value': '0', 'der': 'DZ'}}
PY
ok $? 'DEFINED PARAMETERS y VARIABLES: valor, derivada y un nombre con _'
test "$(grep -c 'no se entiende\|ya esta definid' "$T/par.err")" -eq 6
ok $? 'DEFINED PARAMETERS y VARIABLES: las seis sentencias raras se avisan y no entran'

# OUTPUTS de un .d con la gramatica de salidas.y
$D -v pruebas/salidas-raras.d -o "$T/sal.dat" 2>"$T/sal.err"
python3 - "$T/sal.dat" <<'PY'
import json, sys, re
assert json.load(open(sys.argv[1]))['GLOBAL']['outs'] == 'T:VR1,IR1,VR1'
PY
ok $? 'OUTPUTS: la abscisa de la primera, PLOT y el renombre se sacan'
test "$(grep -c 'OUTPUTS: no se entiende' "$T/sal.err")" -eq 3 && grep -q 'la abscisa (T:) va solo delante' "$T/sal.err"
ok $? 'OUTPUTS: las tres lineas raras se avisan y no entran'

# RUN CONTROLS de un .ds: la forma la lee control.y
$D -v pruebas/controles.ds -o "$T/ctl.dat" 2>"$T/ctl.err"
python3 - "$T/ctl.dat" <<'PY'
import json, sys, re
C = json.load(open(sys.argv[1]))['GLOBAL']['control']
assert C == {'RK': {'value': [3]}, 'GLM': {'value': ['20']}, 'SIRU_PASO': {'value': [0.1]}}, C
PY
ok $? 'RUN CONTROLS de un .ds: CLAVE=[...]'
test "$(grep -c 'RUN CONTROLS: no se entiende' "$T/ctl.err")" -eq 5
ok $? 'RUN CONTROLS de un .ds: las cinco sentencias raras se avisan y no entran'

# la cabeza de ELEMENTS de un .d: la lee rama.y
$D -v pruebas/ramas-raras.d -o "$T/ram.dat" 2>"$T/ram.err"
python3 - "$T/ram.dat" <<'PY'
import json, sys, re
R = json.load(open(sys.argv[1]))['GLOBAL']['ramas']
assert sorted(R) == ['C1', 'E1', 'R1'] and R['C1']['tipo'] == 'CB' and R['R1']['nodos'] == ['1', 'GND'], R
PY
ok $? 'ELEMENTS de un .d: nombre, tipo y nodos'
test "$(grep -c 'linea de ELEMENTS que no se entiende' "$T/ram.err")" -eq 3 && grep -q 'va sin _' "$T/ram.err"
ok $? 'ELEMENTS de un .d: las tres lineas raras se avisan y no entran'

# las construcciones chicas de SCEPTRE: sceptre.y
$D -v pruebas/sceptre-raros.d -o "$T/sce.dat" 2>"$T/sce.err"
python3 - "$T/sce.dat" <<'PY'
import json, sys, re
G = json.load(open(sys.argv[1]))['GLOBAL']
assert G['cotas']['E1'] == {'minimo': 9, 'maximo': 11} and G['cotas']['R1'] == {'porcentaje': 5}, G['cotas']
assert G['ramas']['RAT1']['valor'].strip() == '2.' and 'RAT2' not in G['ramas']
assert G['dc']['sensibilidad'] == [{'dependientes': ['VR1'], 'independientes': ['R1', 'E1']}]
PY
ok $? 'SCEPTRE: cotas, CHANGE de una instancia y conjuntos DC'
grep -q 'la opcion PRINT de una instancia no la hace siru' "$T/sce.err" && grep -q 'T2 = MODEL XX (FOO BAR BAZ): no se entiende' "$T/sce.err" \
  && grep -q 'SENSITIVITY: se esperaba (dependientes / independientes), se saltea: VR1 / R1' "$T/sce.err"
ok $? 'SCEPTRE: lo raro se avisa'

# las expresiones de los valores: expresion.y. Las reescrituras de SCEPTRE
# van en los valores y no en el resto del texto
$D -v pruebas/expresiones-valores.d -o "$T/exv.dat" 2>"$T/exv.err"
python3 - "$T/exv.dat" <<'PY'
import json, sys, re
G = json.load(open(sys.argv[1]))['GLOBAL']
R = G['ramas']
assert R['E1']['valor'].strip() == "Table('EIN.txt','X','Y',T,1)", R['E1']['valor']   # la tabla se define despues
assert R['R1']['valor'].strip() == 'X2 (5.)', R['R1']['valor']                       # EXPRESSION 2 es la etiqueta X2
assert 'EQUATION 3 y TABLE EIN (X)' in G['description']
PY
ok $? 'expresiones: TABLE, EXPRESSION N, y la descripcion no se toca'
grep -q "valor de R2: falta un operador antes de '(4)'" "$T/exv.err"
ok $? 'expresiones: una multiplicacion sin * se avisa'
mkdir -p "$T/exv"
$D --din --directorio "$T/exv" pruebas/expresiones-valores.d 2>/dev/null \
  && grep -q '(3) (4)' "$T/exv/expresiones-valores$QUIEN.din"
ok $? 'expresiones: la multiplicacion sin * llega al .din, que ExprTk la acepta'
mkdir -p "$T/vr"
$D --din --directorio "$T/vr" pruebas/valores-raros.d 2>"$T/vr.err"
[ $? -ne 0 ] && grep -q "valor de R1: no se entiende desde '/ PB' en PA \* / PB" "$T/vr.err"
ok $? 'un valor que no es una expresion se protesta con la rama y desde donde'

$D pruebas/siru3.ds -o "$T/s3.dat" 2>/dev/null
python3 - "$T/s3.dat" <<'PY'
import json, sys, re
G = json.load(open(sys.argv[1]))['GLOBAL']
assert G['defpar']['PDSUM']['value'] == 'X1(PDUM0+PDUM1)' and G['defpar']['PTG']['value'] == 'QTG(TIME,PGD)'
assert G['defpar']['X'] == {'value': '0', 'der': 'DX'}
assert G['control']['RK']['value'] == [3]
assert G['constants'] == {'K': '1E-3', 'J': '5'}
PY
ok $? 'errores de Siru arreglados: D en parametros, RK = [3], 1E-3'

# reglas del manual de SCEPTRE, con los ejemplos de pruebas/sceptre
S=pruebas/sceptre
for f in varias-por-linea continuacion salidas sin-rutina rerun modelo minusculas; do
  $D "$S/$f.d" -o "$T/$f.dat" 2>/dev/null || echo "falla $f"
done
python3 - "$T" <<'PY'
import json, sys, re
G = lambda f: json.load(open(sys.argv[1] + '/' + f + '.dat'))['GLOBAL']
g = G('varias-por-linea')
# la cota (11,13) ya no va en el valor: va a GLOBAL.cotas (PLAN_DC_RERUN paso 1)
assert sorted(g['ramas']) == ['C1', 'E1', 'R1', 'R2'] and g['ramas']['R2']['valor'].strip() == '12'
assert g['cotas']['R2'] == {'minimo': 11, 'maximo': 13}, g.get('cotas')
assert g['defpar']['P2']['value'] == 'X1(P1*3.)' and g['control']['RK']['value'] == [12]
assert any(e.get('eq', '').replace(' ', '') == 'not(VC1>5)' for e in g['hequations'])
g = G('continuacion')
assert g['defpar']['PSUM']['value'] == 'X1(IR1+IC1)' and g['ramas']['R1']['valor'].strip() == '5' and g['outs'] == 'T:VC1,IR1'
assert G('salidas')['outs'] == 'T:VR1,IR1,VC1,IC1,IC14,VC14'
assert G('sin-rutina')['control']['RK']['value'] == [13]
PY
ok $? 'SCEPTRE: sentencias por coma, STOP IF, continuacion, OUTPUTS, XPO por defecto'
python3 - "$T" <<'PY'
import json, sys, re
G = lambda f: json.load(open(sys.argv[1] + '/' + f + '.dat'))['GLOBAL']
g = G('rerun')
assert g['defpar']['TIEMPOFINAL']['value'] == '500' and g['control']['RK']['value'] == [12]
g = G('modelo')
# el modelo XXXX (1-7-8-9) en T8, 10-11-12-13, con CHANGE R1 = 2. (manual 2.3)
r = {k: (v['nodos'], v['valor'].strip()) for k, v in g['ramas'].items()}
assert r == {'C1T8': (['2T8', '3T8'], "Table('1T8.txt','X','Y',VC1T8,1)"),
             'E1T8': (['13', '10'], '20'), 'J1T8': (['12', '11'], 'Q1T8 (VC1T8, P1T8)'),
             'R1T8': (['10', '2T8'], '2.'), 'RA': (['10', 'GND'], '1'),
             'RB': (['11', 'GND'], '1'), 'RC': (['12', 'GND'], '1'), 'RD': (['13', 'GND'], '1')}, r
assert g['funciones'] == {'Q1T8': ['Q1T8', '(3.*A + B)', 0, ['A', 'B']]}, g['funciones']
v = [float(x) for x in re.split(r'[\s,]+', g['tablas']['1T8']['texto']) if x]
assert [v[i:i + 2] for i in range(0, len(v), 2)] == [[-5, 1], [0, 2], [1, 5], [2, 10]], g['tablas']['1T8']
assert g['defpar']['P1T8']['value'] == '2.7' and 'P1' not in g['defpar']
g = G('minusculas')
assert sorted(g['ramas']) == ['c1', 'e1', 'r1'] and g['control']['RK']['value'] == [12]
PY
ok $? 'SCEPTRE: RERUN no entra a la corrida principal; MODEL se expande con el sufijo; minusculas'

# Las opciones DC y los reruns de SCEPTRE (siru/PLAN_DC_RERUN.md, paso 1):
# los conjuntos de SENSITIVITY, MONTE CARLO y WORST CASE en GLOBAL.dc, las
# cotas de ELEMENTS y DEFINED PARAMETERS en GLOBAL.cotas (fuera del valor),
# sus RUN CONTROLS como controles SCEPTRE_*, y cada RERUN DESCRIPTION y el
# CONTINUE con sus lineas tal cual
$D "$S/dc-opciones.d" -o "$T/dc-opciones.dat" 2>/dev/null || echo "falla dc-opciones"
python3 - "$T" <<'PY'
import json, sys, re
G = lambda f: json.load(open(sys.argv[1] + '/' + f + '.dat'))['GLOBAL']
g = G('dc-opciones')
assert g['dc'] == {'sensibilidad': [{'dependientes': ['VC1', 'PABC'], 'independientes': ['R1', 'E1']}],
                   'montecarlo': [{'dependientes': ['VC1'], 'independientes': ['R1', 'R2']}],
                   'peorcaso': [{'dependientes': ['VC1'], 'independientes': ['R1', 'R2']}]}, g['dc']
assert g['cotas'] == {'R1': {'porcentaje': 10}, 'R2': {'minimo': 11, 'maximo': 13},
                      'J1': {'minimo': 0.5, 'maximo': 2}, 'P1': {'minimo': 1.5, 'maximo': 3}}, g['cotas']
assert g['ramas']['R2']['valor'].strip() == '12' and g['defpar']['P1']['value'] == '2'
c = {k: v['value'] for k, v in g['control'].items()}
assert c == {'RK': [12], 'SCEPTRE_IC': [1], 'SCEPTRE_SENS': [1], 'SCEPTRE_MC': [20], 'SCEPTRE_DIST': [2],
             'SCEPTRE_SEMILLA': [127263527], 'SCEPTRE_WC': [3]}, c
g = G('rerun')
assert g['reruns'] == [{'n': 2, 'cambios': [{'seccion': 'ELEMENTS', 'linea': 'R1 = 9.3, 9.5'},
                                           {'seccion': 'RUN CONTROLS', 'linea': 'STOP TIME = 600, 700'},
                                           {'seccion': 'RUN CONTROLS', 'linea': 'INTEGRATION ROUTINE = XPO, RUK'}]}], g['reruns']
assert g['continuar'] == [{'seccion': 'RUN CONTROLS', 'linea': 'STOP TIME = 2000'}], g['continuar']
PY
ok $? 'SCEPTRE: opciones DC (conjuntos, cotas, controles) y RERUN/CONTINUE con sus lineas'

# las formas de tabla del manual de SCEPTRE (2.2.1, 2.2.6): TABLE NOMBRE en
# varias lineas, TABLE N, x, y... en una, la forma corta TN = x, y..., y en
# los valores TABLE N (ARG), TN(ARG) y TABLE NOMBRE sola (del tiempo)
$D "$S/tablas-formas.d" -o "$T/tablas-formas.dat" 2>/dev/null
python3 - "$T" <<'PY'
import json, sys, re
g = json.load(open(sys.argv[1] + '/tablas-formas.dat'))['GLOBAL']
def pares(x):
    v = [float(n) for n in re.split(r'[\s,]+', x['texto']) if n]
    return [v[i:i + 2] for i in range(0, len(v), 2)]
t = {k: pares(v) for k, v in g['tablas'].items()}
assert t == {'ERIN': [[0, 0], [1, 1], [2, 1], [2, 1.5], [3, 1.5], [3, 0], [4, 0]],
             '3': [[0, 1], [10, 2]], '5': [[0, 1], [5, 2], [10, 3]]}, t
v = {k: r['valor'].strip() for k, r in g['ramas'].items()}
assert v['E1'] == "Table('ERIN.txt','X','Y',T,1)", v
assert v['R1'] == "Table('3.txt','X','Y',T,1)", v
assert v['C1'] == "Table('5.txt','X','Y',VC1,1)", v
PY
ok $? 'SCEPTRE: TABLE en una linea, forma corta TN =, TN(ARG) y TABLE sola'

# los diodos (manual 2.2.2 y las formas cortas): la corriente en funcion de
# la tension de la propia fuente, y la fuente que depende de un diodo usa su
# corriente
$D "$S/diodos.d" -o "$T/diodos.dat" 2>/dev/null
python3 - "$T" <<'PY'
import json, sys, re
v = {k: r['valor'].strip() for k, r in json.load(open(sys.argv[1] + '/diodos.dat'))['GLOBAL']['ramas'].items()}
assert v['J18'] == '(1.E-7)*(EXP(( 30.)*VJ18)-1)', v
assert v['J19'] == '(1.E-7)*(EXP(( 30.)*VJ19)-1)', v
assert v['J20'] == "Table('1.txt','X','Y',VJ20,1)" and v['J21'] == "Table('1.txt','X','Y',VJ21,1)", v
assert v['JA'] == '.98 * IJ18' and v['JK'] == '2. * IR1', v
PY
ok $? 'SCEPTRE: DIODE EQUATION, DIODE Q, DIODE TABLE, DIODE T y la fuente que depende de un diodo'


# corrida: lee las secciones comentadas del .din, como hace siru/bin/siru
corrida() {
  python3 -c '
import sys
secc, c = "", {"parametros": {}, "estados": [], "corrida": {}, "adaptacion": {}}
for l in open(sys.argv[1]):
    l = l.rstrip()
    if not l.lstrip().startswith("#"): continue
    l = l.lstrip()[1:].strip()
    if l in ("corrida", "adaptacion", "parametros", "estados"): secc = l; continue
    if not secc: continue
    p = l.split()
    if len(p) < 2: secc = ""; continue
    if secc == "estados": c["estados"].append(p)
    else: c[secc][p[0]] = p[1]
print(repr(c))
' "$1"
}

# ---- el .din, que sale del arbol sin .dat intermedio -------------------
D=$T/din
mkdir -p "$D"
$P --din --directorio "$D" pruebas/circuitoB.ds 2>"$T/d.err"
ok $? 'circuitoB.ds da su .din'
test ! -s "$T/d.err"
ok $? 'sin -v el .din no escribe nada por stderr'
cmp -s "$D/circuitoB$QUIEN.din" pruebas/circuitoB.esperado.din
ok $? 'el .din base es el esperado'
cmp -s "$D/circuitoB$QUIEN-ED3.din" pruebas/circuitoB-ED3.esperado.din
ok $? 'el .din del metodo 3 es el esperado'
corrida "$D/circuitoB$QUIEN-ED3.din" | python3 -c '
import sys
c = eval(sys.stdin.read())
assert [e[0] for e in c["estados"]] == ["qc3","il2","il23"]   # el orden de las ramas
assert [e[1] for e in c["estados"]] == ["p_qc3","p_il2","p_il23"]
# el paso por omision es la milesima parte del tiempo final, que aqui es 1
# (siru r2365, 06/10/2026); antes era 0.1 fijo
assert float(c["corrida"]["desde"]) == 0 and float(c["corrida"]["paso"]) == 0.001
'
ok $? 'la corrida del .din nombra los estados'
test -z "$(ls "$D"/*.json 2>/dev/null)"
ok $? 'el .din lleva la corrida adentro: no se escribe ningun .json'

$P --din --directorio "$D" pruebas/tablas.d 2>/dev/null
cmp -s "$D/tablas$QUIEN.din" pruebas/tablas.esperado.din
ok $? 'las tablas del .d van adentro del .din'

# 'X = 0 D:DX' con la derivada en EQUATIONS ('DX + X'): X es un estado
# (antes no se registraba y salia un punto; cambiofase.ds, 01/10/2026)
$P --din --directorio "$D" pruebas/siru3.ds 2>"$T/s3.err"
test $? -eq 0 && grep -q "s0_dx + s0_x = 0" "$D/siru3$QUIEN-ED3.din"
ok $? 'X = 0 D:DX con DX en EQUATIONS es un estado: sale el .din de RK3 con dx + x = 0'

$P --din 10 --directorio "$T" pruebas/circuitoB.ds 2>"$T/rk10.err"
test $? -eq 0 && grep -q 'q_il2 = il2 + dt' "$T/circuitoB$QUIEN-ED10.din"
ok $? 'el RK con bc genera una segunda estimacion'
corrida "$T/circuitoB$QUIEN-ED10.din" | python3 -c '
import sys
c = eval(sys.stdin.read())
assert all(e[3] == "q_" + e[0] for e in c["estados"])
'
ok $? 'la corrida nombra cada estimacion'

# .d de SCEPTRE con controles de paso y extensiones explicitas de siru.
awk '
  /^STOP TIME =/ { print "STOP TIME = 0.2"; next }
  /^END$/ {
    print "STARTING STEP SIZE = 0.08"
    print "MINIMUM STEP SIZE = 0.000001"
    print "MAXIMUM STEP SIZE = 0.1"
    print "SIRU ADAPTIVE STEP = 1"
    print "SIRU ABSOLUTE TOLERANCE = 0.00001"
    print "SIRU RELATIVE TOLERANCE = 0.001"
    print "SIRU ERROR ORDER = 2"
  }
  { print }
' pruebas/6elementos.d >"$T/adaptacion.d"
$P --din --directorio "$T" "$T/adaptacion.d" 2>"$T/adaptacion.err"
ok $? 'un .d con controles adaptativos genera el sistema'
corrida "$T/adaptacion$QUIEN-ED12.din" | python3 -c '
import sys
c = eval(sys.stdin.read())
a, r = c["adaptacion"], c["corrida"]
assert float(r["paso"]) == 0.08 and float(r["hasta"]) == 0.2
assert a["metodo"] == "embebido" and float(a["orden_error"]) == 2
assert float(a["absoluto"]) == 1e-5 and float(a["relativo"]) == 1e-3
assert float(a["paso_min"]) == 1e-6 and float(a["paso_max"]) == 0.1
assert all(e[3] == "q_" + e[0] for e in c["estados"])
'
ok $? 'los controles del .d llegan a la seccion adaptacion'
awk '{gsub("INTEGRATION ROUTINE = TRAP", "INTEGRATION ROUTINE = RUK"); print}' \
  "$T/adaptacion.d" >"$T/adaptacion-doble.d"
$P --din --directorio "$T" "$T/adaptacion-doble.d" 2>"$T/adaptacion-doble.err"
corrida "$T/adaptacion-doble$QUIEN-ED7.din" | python3 -c '
import sys
c = eval(sys.stdin.read())
assert c["adaptacion"]["metodo"] == "doble"
assert all(e[3] == "-" for e in c["estados"])
'
ok $? 'sin bc el .d selecciona dos medios pasos'
$P no-existe.ds -o "$T/x.dat" 2>/dev/null
test $? -ne 0
ok $? 'un archivo que no existe da error'

# Llaves, lazos de capacitores y cortes de bobinas: el sistema depende de la
# configuracion, que se da con --llaves (siruviejo/LLAVES.md 13). Son casos de mdin:
# no dejan .din sino NOMBRE-<bits>.mdin, -<bits>I.mdin (instante) y -<bits>E.mdin
# (equilibrio). llave-lazo.ds y
# llave-corte.ds son los de siru/corpus/corpus/llaves_equilibrio.
mkdir "$T/ll"
$P --directorio "$T/ll" pruebas/llave-lazo.ds --din 2>"$T/ll/err"
test $? -ne 0 && grep -q 'falta el estado de S1' "$T/ll/err"
ok $? 'con llaves y sin --llaves, el error dice que falta la configuracion'
$P pruebas/llave-lazo.ds --topologia > "$T/ll/topo" 2>/dev/null
grep -qx 'llaves S1' "$T/ll/topo" && grep -qx 'configuracion incompleta' "$T/ll/topo"
ok $? '--topologia dice que llaves hay, sin necesitar su estado'
$P pruebas/llave-lazo.ds --topologia --llaves S1=1 2>/dev/null | grep -qx 'lazos C2'
ok $? '--topologia con la llave cerrada: el lazo de capacitores'
$P pruebas/llave-lazo.ds --topologia --llaves S1=0 2>/dev/null | grep -qx 'lazos'
ok $? '--topologia con la llave abierta: sin lazo'
$P --directorio "$T/ll" pruebas/llave-lazo.ds --din --llaves S1=0 2>/dev/null
ok $? 'llave abierta: se arma el sistema'
test ! -e "$T/ll/llave-lazo-0E.mdin"
ok $? 'llave abierta: no hay equilibrio que escribir'
grep -q '^  vs1 \* s1 + is1 \* (1-s1) = 0' "$T/ll/llave-lazo-0I.mdin" \
  && grep -q '^  s1 = 0$' "$T/ll/llave-lazo-0I.mdin"
ok $? 'la llave es una rama, con x1 = 0 abierta'
grep -q '^  llave_s1 = (t<1)+(t>3)$' "$T/ll/llave-lazo-0I.mdin" \
  && grep -q '^#   S1 llave_s1$' "$T/ll/llave-lazo-0I.mdin"
ok $? 'el instante lleva la condicion de la llave y la seccion llaves'
$P --directorio "$T/ll" pruebas/llave-lazo.ds --din --llaves S1=1 2>/dev/null
grep -q '^  (dqc2dt)/(c2) -(dqc1dt)/(c1) = 0' "$T/ll/llave-lazo-1I.mdin"
ok $? 'llave cerrada: la ley del lazo va en derivadas'
grep -q '^  +(qc1 - pre_qc1) +(qc2 - pre_qc2) = 0$' "$T/ll/llave-lazo-1E.mdin" \
  && grep -q '^#   qc1 pre_qc1$' "$T/ll/llave-lazo-1E.mdin"
ok $? 'llave cerrada: el equilibrio conserva la carga del lazo'
$P --directorio "$T/ll" pruebas/llave-corte.ds --din --llaves S1=0 2>/dev/null
grep -q '^  -dil1dt +dil2dt = 0' "$T/ll/llave-corte-0I.mdin" \
  && grep -q '^  -il1 +il2 = 0$' "$T/ll/llave-corte-0E.mdin"
ok $? 'corte de bobinas: la ley del corte en derivadas, y su equilibrio'

# Un .dat como entrada: ya es el dato, y el sistema sale del mismo libdin
$P pruebas/llave-lazo.ds -o "$T/ll/congelado.dat" 2>/dev/null
$P "$T/ll/congelado.dat" --topologia --llaves S1=1 2>/dev/null | grep -qx 'lazos C2'
ok $? 'un .dat como entrada: --topologia'
$P --directorio "$T/ll" "$T/ll/congelado.dat" --din --llaves S1=1 2>/dev/null \
  && cmp -s "$T/ll/congelado-1.mdin" "$T/ll/llave-lazo-1.mdin"
ok $? 'un .dat como entrada: el mismo sistema que desde el .ds'
$P "$T/ll/congelado.dat" 2>/dev/null
test $? -ne 0
ok $? 'un .dat sin --din ni --topologia es un error: ya es un .dat'

# Un circuito con ecuaciones propias, y lo que el archivo recibe del
# simulador: T, el paso (DT) y la cuenta de pasos (TICK). siruviejo/LLAVES.md 13.
mkdir "$T/pr"
$P --directorio "$T/pr" pruebas/propias.d --din 2>"$T/pr/err"
ok $? 'un circuito con una ecuacion propia se arma'
! grep -q PENDIENTE "$T/pr/propias$QUIEN.din" "$T/pr/propias$QUIEN-ED"*.din
ok $? 'ni la ecuacion propia, ni la constante sin usar, ni la parada quedan PENDIENTE'
grep -q '^  z+t+tick-dt = 0' "$T/pr/propias$QUIEN.din" && grep -q '^  z = 0$' "$T/pr/propias$QUIEN.din"
ok $? 'la ecuacion propia es una ecuacion mas, con su incognita de VARIABLES'
grep -q '^#   dt 0.1' "$T/pr/propias$QUIEN.din" && ! grep -q '^#   dt ' "$T/pr/propias$QUIEN-ED"*.din \
  && grep -q '^  dt = 0.1 \[s\] > 0$' "$T/pr/propias$QUIEN-ED"*.din
ok $? 'DT es el paso del simulador, no el 0.01 que dice CONSTANTS'
grep -q '^#   pasos tick$' "$T/pr/propias$QUIEN-ED"*.din && ! grep -q '^#   tick ' "$T/pr/propias$QUIEN-ED"*.din
ok $? 'TICK es la cuenta de pasos: la pone el simulador, no es un parametro'
grep -q '^  seguir_1 = tick<=1000$' "$T/pr/propias$QUIEN.din" \
  && grep -q '^#   seguir_1 TICK<=1000$' "$T/pr/propias$QUIEN.din" \
  && grep -q '^#   hasta 10$' "$T/pr/propias$QUIEN-ED"*.din
ok $? 'las paradas: T>10 es el hasta, y TICK>1000 la evalua el instante'
$P pruebas/propias.d --topologia 2>/dev/null | grep -qx 'paradas 1'
ok $? '--topologia dice cuantas condiciones de parada hay'

# Una capacidad que depende del voltaje de su propio capacitor, en un lazo de
# capacitores: C1 = 1 + 0.5*VC1. La ley del lazo divide por la capacidad
# diferencial dQ/dV, no por C, y el equilibrio escribe el voltaje con los
# potenciales.
mkdir "$T/cv"
$P --directorio "$T/cv" pruebas/capacidad-variable.d --din 2>"$T/cv/err"
ok $? 'un lazo con una capacidad que depende de su voltaje se arma'
grep -q '^  (s0_dqc2dt)/(c2) -(s0_dqc1dt)/((((1+0.5\*(s0_vc1+1e-6)))\*(s0_vc1+1e-6) - ((1+0.5\*(s0_vc1-1e-6)))\*(s0_vc1-1e-6))/(2\*1e-6)) = 0' "$T/cv/capacidad-variable.mdin"
ok $? 'la ley del lazo divide por dQ/dV, calculada numericamente'
grep -q '^  (1+0.5\*(w2 - 0))\*(w2 - 0) - qc1 = 0$' "$T/cv/capacidad-variable-E.mdin"
ok $? 'el equilibrio escribe el voltaje del capacitor con los potenciales'
sed 's/= 1+0.5\*VC1/= 1+0.5*VR1/' pruebas/capacidad-variable.d > "$T/cv/ajeno.d"
$P --directorio "$T/cv" "$T/cv/ajeno.d" --din 2>"$T/cv/err2"
test $? -ne 0 && grep -q 'la capacidad de C1 depende de vr1' "$T/cv/err2"
ok $? 'una capacidad que depende de un voltaje ajeno, en un lazo, es un error que lo dice'

# La inductancia mutua de SCEPTRE: 'M, L1-L2 = 1.8'. No es una rama: le agrega
# el termino cruzado a las dos bobinas (siruviejo/LLAVES.md 13).
mkdir "$T/mu"
$P pruebas/mutua.d -o "$T/mu/mutua.dat" 2>/dev/null
python3 - "$T/mu/mutua.dat" <<'PY'
import json, sys, re
G = json.load(open(sys.argv[1]))['GLOBAL']
assert 'M' not in G['ramas']
assert G['mutuas'] == {'M': {'bobinas': ['L1', 'L2'], 'valor': '1.8'}}
PY
ok $? 'la M va en GLOBAL.mutuas, no entre las ramas'
$P --directorio "$T/mu" pruebas/mutua.d --din 2>/dev/null
grep -q '^  dil1dt \* l1 - vl1 + m \* dil2dt = 0' "$T/mu/mutua$QUIEN.din" \
  && grep -q '^  dil2dt \* l2 - vl2 + m \* dil1dt = 0' "$T/mu/mutua$QUIEN.din" \
  && grep -q '^  m = 1.8$' "$T/mu/mutua$QUIEN.din"
ok $? 'cada bobina lleva M por la derivada de la corriente de la otra'
sed 's/^    L2,          3-GND = 4/    LNL-L2,      3-GND = 4,1/' pruebas/mutua.d > "$T/mu/otra.d"
$P --directorio "$T/mu" "$T/mu/otra.d" --din 2>"$T/mu/err"
test $? -ne 0 && grep -q 'no es una bobina con el modelo de la biblioteca' "$T/mu/err"
ok $? 'con una bobina de otro modelo no se sabe donde va el termino, y lo dice'

# El transformador con nucleo: devanados MW de la biblioteca, y el nucleo como
# estado propio del circuito (VARIABLES DPHI1) con su ecuacion propia.
mkdir "$T/nu"
$P --directorio "$T/nu" pruebas/nucleo.d --din 2>"$T/nu/err"
ok $? 'un transformador con nucleo se arma'
grep -q '^# @estado phi1 dphi1$' "$T/nu/nucleo$QUIEN.din" \
  && grep -q '^  vmw1 - mw1 \* (dphi1) = 0' "$T/nu/nucleo$QUIEN.din" \
  && grep -q '^  vmw2 - mw2 \* (dphi1) = 0' "$T/nu/nucleo$QUIEN.din" \
  && grep -q '^  1\*imw1 + 2\*imw2 - rel\*phi1 = 0' "$T/nu/nucleo$QUIEN.din"
ok $? 'el flujo es un estado propio del circuito, y los devanados comparten su derivada'
grep -q '^#   phi1 p_phi1 0 ' "$T/nu/nucleo$QUIEN-ED12.din" && ! grep -q '^#   phi1 ' <(sed -n '/^# parametros/,/^$/p' "$T/nu/nucleo$QUIEN-ED12.din")
ok $? 'PHI1 se integra: esta en estados y no en parametros'

# Un nodo aislado por las llaves abiertas: su potencial va a cero en lugar de
# su ley de corrientes, que es redundante.
mkdir "$T/is"
$P --directorio "$T/is" pruebas/isla.d --din --llaves S1=0,S2=0 2>/dev/null
grep -q '^  u3 = 0   # @lineal' "$T/is/isla-00I.mdin" && ! grep -q '^  -is1 +is2 = 0' "$T/is/isla-00I.mdin"
ok $? 'con las dos llaves abiertas, el nodo 3 queda a potencial cero'
$P --directorio "$T/is" pruebas/isla.d --din --llaves S1=0,S2=1 2>/dev/null
! grep -q '^  u3 = 0   # @lineal' "$T/is/isla-01I.mdin" && grep -q '^  -is1 +is2 = 0' "$T/is/isla-01I.mdin"
ok $? 'con S2 cerrada el nodo 3 no esta aislado y su ley de corrientes queda'

# Una parte propia en DEFINED PARAMETERS de un circuito: estados por prefijo
# D, un parametro definido por una expresion, una tabla pegada (TABLEEX) y una
# etiqueta de expresion con letras (XA).
mkdir "$T/pp"
$P --directorio "$T/pp" pruebas/parametros-propios.d --din 2>"$T/pp/err"
ok $? 'un circuito con estados y parametros propios en DEFINED PARAMETERS se arma'
grep -q '^# @estado px1 dpx1$' "$T/pp/parametros-propios$QUIEN.din" \
  && grep -q '^  dpx2 - (-px1) = 0' "$T/pp/parametros-propios$QUIEN.din"
ok $? 'DPX1 y DPX2 hacen estados PX1 y PX2 tambien con circuito'
grep -q "^  pdis - (tabla('EX.txt','X','Y',px1,1)) = 0" "$T/pp/parametros-propios$QUIEN.din"
ok $? 'PDIS = TABLEEX(PX1) es una incognita propia con su ecuacion, y TABLEEX es TABLE EX'
grep -q '^  vea + ( (pdis\*2)) = 0' "$T/pp/parametros-propios$QUIEN.din"
ok $? 'XA (PDIS*2) es la expresion misma'

# El elemento nulo: un tipo propio cuyo modelo es 0 no aporta ecuacion, y las
# propias del circuito fijan su tension y su corriente (un transformador
# ideal, engrane.d).
mkdir "$T/nl"
$P --directorio "$T/nl" pruebas/nulo.d --din 2>"$T/nl/err"
test -s "$T/nl/nulo$QUIEN.din" && ! grep -q '^  0 = 0' "$T/nl/nulo$QUIEN.din" \
  && grep -q '^  vmzer1/1 - vmzer2/2 = 0' "$T/nl/nulo$QUIEN.din"
ok $? 'el elemento nulo no escribe 0 = 0, y las ecuaciones propias lo gobiernan'
# Un fasor es la excitacion del analisis AC de SCEPTRE (RUN AC): la rama vale
# 0 en el tiempo y la magnitud y la fase van a GLOBAL.ac. Antes se rechazaba.
printf 'CIRCUIT DESCRIPTION\nELEMENTS\n  EAC, GND-1 = (1,0.5)\n  R1, 1-GND = 1\nRUN CONTROLS\n  RUN AC\n  INITIAL FREQUENCY = 100\n  FINAL FREQUENCY = 1000\n  NUMBER FREQUENCY STEPS = 4\n  TYPE FREQUENCY RUN = LOG\nEND\n' > "$T/nl/ac.d"
$P "$T/nl/ac.d" -o - 2>"$T/nl/err2" | python3 -c '
import json, sys, re
g = json.load(sys.stdin)["GLOBAL"]
assert g["ac"] == {"EAC": [1, 0.5]}, g.get("ac")
assert g["ramas"]["EAC"]["valor"].strip() == "AC_EAC", g["ramas"]["EAC"]["valor"]
assert g["defpar"]["AC_EAC"]["value"] == "0", g["defpar"].get("AC_EAC")
c = {k: v["value"] for k, v in g["control"].items()}
for k, v in (("SCEPTRE_AC", [1]), ("SCEPTRE_FREQ_INI", [100]), ("SCEPTRE_FREQ_FIN", [1000]),
             ("SCEPTRE_FREQ_PASOS", [4]), ("SCEPTRE_FREQ_TIPO", [2])):
    assert c.get(k) == v, (k, c.get(k))'
ok $? 'un valor (1,0.5) es el fasor del analisis AC: la rama vale el parametro AC_EAC = 0 y va a GLOBAL.ac, con el barrido'

# El nombre del parametro es mas largo que el valor: '(1,0)' son seis bytes
# y AC_EINLARGO doce. Escribirlo sobre el texto del valor desbordaba el
# bloque (r2292 a r2295, lo encontro la otra sesion con c14.ds2 y FORTIFY).
printf 'CIRCUIT DESCRIPTION\nELEMENTS\n  EINLARGO, 1-GND =(1,0)\n  R1, 1-GND = 1\nEND\n' > "$T/nl/aclargo.d"
$P "$T/nl/aclargo.d" -o - 2>/dev/null | python3 -c '
import json, sys, re
g = json.load(sys.stdin)["GLOBAL"]
assert g["ac"] == {"EINLARGO": [1, 0]}, g.get("ac")
assert g["ramas"]["EINLARGO"]["valor"].strip() == "AC_EINLARGO", g["ramas"]["EINLARGO"]["valor"]'
ok $? 'el parametro del fasor mas largo que el valor no desborda el texto'

# Un nombre que ExprTk se reserva no puede ser una variable del sistema: se
# protesta nombrandolo, para cambiarlo en el archivo (i14.d tenia un
# parametro PI, una corriente; decision del autor, 05/10/2026)
printf 'CIRCUIT DESCRIPTION\nELEMENTS\n  E1, GND-1 = 1\n  R1, 1-2 = PI\n  R2, 2-GND = 1\nDEFINED PARAMETERS\n  PI = 3\nOUTPUTS\n  VR1\nRUN CONTROLS\n  STOP TIME = 1\nEND\n' > "$T/nl/reservado.d"
$P --directorio "$T/nl" "$T/nl/reservado.d" --din 2>"$T/nl/err3"
test $? -ne 0 && grep -q 'PI es un nombre reservado de ExprTk' "$T/nl/err3"
ok $? 'un parametro que se llama PI se protesta: ExprTk reserva ese nombre'

# Los parentesis que no cierran: SCEPTRE los acepta, ExprTk no, y se protesta
# en lugar de adivinar (c08.d y d01.d tenian un ) de mas; pedido del autor)
printf 'CIRCUIT DESCRIPTION\nELEMENTS\n  E1, GND-1 = 1\n  R1, 1-2 = (20*LOG10(ABS(100))))\n  C1, 2-GND = 1\nOUTPUTS\n  VR1\nRUN CONTROLS\n  STOP TIME = 1\nEND\n' > "$T/nl/parentesis.d"
$P --directorio "$T/nl" "$T/nl/parentesis.d" --din 2>"$T/nl/err4"
test $? -ne 0 && grep -q 'los parentesis no cierran' "$T/nl/err4"
ok $? 'una expresion con un ) de mas se protesta, no se adivina'

# El valor de una rama puede ser un parametro definido por una EXPRESION, no
# solo por un numero: 'L1, 2-GND = PL1' con 'PL1 = X1 (PMU1*PS)' en v040.d,
# la inductancia de una bobina que depende de la corriente por una tabla de
# permeabilidad. Antes fallaba con 'se esperaba un numero finito'.
printf 'CIRCUIT DESCRIPTION\nELEMENTS\n  E1, GND-1 = 1\n  R1, 1-2 = 1\n  L1, 2-GND = PL1\nDEFINED PARAMETERS\n  PL1 = X1 (PK*2)\n  PK = 3\nOUTPUTS\n  IL1\nRUN CONTROLS\n  STOP TIME = 1\nEND\n' > "$T/nl/parexpr.d"
$P --directorio "$T/nl" "$T/nl/parexpr.d" --din 2>"$T/nl/err5"
test -s "$T/nl/parexpr$QUIEN.din" && grep -q '^  pl1 - ((pk\*2)) = 0' "$T/nl/parexpr$QUIEN.din" \
  && grep -q 'dil1dt \* (pl1)' "$T/nl/parexpr$QUIEN.din"
ok $? 'el valor de una rama puede ser un parametro definido por una expresion'

# un .d que incluye otro .d: el incluido se lee como .d (sus ramas sin tipo),
# no como .ds, que es como se leen las bibliotecas (04/10/2026)
printf 'ELEMENTS\n E1, GND-1 = 1\ninclude parte-incluida.d\n R1, 1-GND = 2\nOUTPUTS\n VR1\nEND\n' > "$T/incluye.d"
printf ' R2, 1-GND = 2\n' > "$T/parte-incluida.d"
$P "$T/incluye.d" -o - 2>/dev/null | python3 -c '
import json, sys, re
assert sorted(json.load(sys.stdin)["GLOBAL"]["ramas"]) == ["E1", "R1", "R2"]'
ok $? 'un .d incluido desde un .d se lee como .d'

# Los nombres de antes. Hasta el 05/10/2026 las claves SIRU_* de un .ds eran
# SIMUL2_*, y los controles SIRU ... de un .d, SIMUL2 ...: un circuito
# escrito asi da el mismo dato que con los nombres de ahora, y un .dat que
# trae las claves viejas, el mismo sistema. (El include del .ds es relativo
# al archivo: en la copia va absoluto.)
mkdir "$T/na" "$T/na/v" "$T/na/n"
sed -e 's/^SIMUL2_/SIRU_/' -e "s|^include \\.\\./biblioteca/|include $AQUI/biblioteca/|" pruebas/nombres-de-antes.ds > "$T/na/ahora.ds"
$P pruebas/nombres-de-antes.ds -o "$T/na/antes.dat" 2>/dev/null && $P "$T/na/ahora.ds" -o "$T/na/ahora.dat" 2>/dev/null \
  && igual "$T/na/antes.dat" "$T/na/ahora.dat" && python3 - "$T/na/antes.dat" <<'PY'
import json, sys, re
c = json.load(open(sys.argv[1]))["GLOBAL"]["control"]
assert c["SIRU_PASO"]["value"] == [0.05] and c["SIRU_ADAPT"]["value"] == [0], c
assert not [k for k in c if k.startswith("SIMUL2")], c
PY
ok $? 'un .ds con las claves SIMUL2_* da el mismo dato, con las claves SIRU_*'
sed 's/^SIMUL2 /SIRU /' pruebas/nombres-de-antes.d > "$T/na/ahora.d"
$P pruebas/nombres-de-antes.d -o "$T/na/antes-d.dat" 2>/dev/null && $P "$T/na/ahora.d" -o "$T/na/ahora-d.dat" 2>/dev/null \
  && igual "$T/na/antes-d.dat" "$T/na/ahora-d.dat" && python3 - "$T/na/antes-d.dat" <<'PY'
import json, sys, re
c = json.load(open(sys.argv[1]))["GLOBAL"]["control"]
assert c["SIRU_ADAPT"]["value"] == [1] and c["SIRU_ABSOLUTO"]["value"] == [1e-5], c
assert c["SIRU_RELATIVO"]["value"] == [1e-3] and c["SIRU_ORDEN_ERROR"]["value"] == [2], c
assert not [k for k in c if k.startswith("SIMUL2")], c
PY
ok $? 'un .d con los controles SIMUL2 ... da el mismo dato, con las claves SIRU_*'
$P -v pruebas/nombres-de-antes.ds -o /dev/null 2>&1 | grep -q 'SIMUL2_PASO es el nombre anterior de SIRU_PASO'
ok $? 'con -v avisa que el nombre es el anterior'
cp "$T/na/ahora.dat" "$T/na/n/c.dat"
sed 's/"SIRU_/"SIMUL2_/g' "$T/na/ahora.dat" > "$T/na/v/c.dat"
grep -q '"SIMUL2_PASO"' "$T/na/v/c.dat" \
  && $P --din --directorio "$T/na/n" "$T/na/n/c.dat" 2>/dev/null && $P --din --directorio "$T/na/v" "$T/na/v/c.dat" 2>/dev/null \
  && grep -q '^# *paso 0.05' "$T/na/n/c$QUIEN-ED3.din" \
  && for f in "$T/na/n"/*.din; do cmp -s "$f" "$T/na/v/$(basename "$f")" || exit 1; done
ok $? 'un .dat con las claves SIMUL2_* da el mismo sistema'

# SPLIT debe conservar mapas textuales y generar un din autosuficiente;
# estas pruebas no necesitan Calcula ni el corpus de siru.
mkdir "$T/split"
$P --din --directorio "$T/split" pruebas/split.ds 2>"$T/split.err" && \
python3 - "$T/split/split$QUIEN-SPLIT2.din" <<'PY'
import json, sys
s = open(sys.argv[1]).read()
d = json.loads(next(l.split("descriptor ", 1)[1] for l in s.splitlines() if l.startswith("#   descriptor ")))
assert d["familia"] == "SPLIT" and d["version"] == 1 and d["id"] == 2
assert d["secuencia"] == [["a", .5], ["b", 1], ["a", .5]]
assert d["flujos"]["a"]["q"] == "q+split_h*p"
assert "#   metodo SPLIT" in s and "split_p_q" in s and "split_flujo" in s
PY
ok $? 'SPLIT: Strang conserva mapas, secuencia y datos del subflujo en el din'
sed 's/SPLIT=\[2\]/SPLIT=[1]/' pruebas/split.ds > "$T/lie.ds"
$P --din --directorio "$T/split" "$T/lie.ds" 2>/dev/null && test -s "$T/split/lie$QUIEN-SPLIT1.din"
ok $? 'SPLIT: Lie-Trotter usa su propio selector y archivo'
sed 's/SIRU_ADAPT=\[0\]/SIRU_ADAPT=[1]/' pruebas/split.ds > "$T/split-malo.ds"
! $P --din --directorio "$T/split" "$T/split-malo.ds" 2>"$T/split-malo.err"
ok $? 'SPLIT: no sustituye silenciosamente adaptacion no implementada'
grep -q 'SIRU_ADAPT' "$T/split-malo.err"
ok $? 'SPLIT: diagnostico de paso fijo explicito'

echo
if [ $fallan -eq 0 ]; then echo "$n pruebas, todas bien"; else echo "$fallan de $n pruebas FALLAN"; fi
exit $((fallan > 0))
