unit utipos_xcf;
{ utipos_xcf   rch@20170221
Esta unidad define las clases necesarias para soporte de información del tipo
ramilletes de crónicas de evaluación de una función CF(X, k) con k el índice del
tiempo.
}

{$mode delphi}

interface

uses
  Classes, SysUtils,
  math,
  useriestemporales,
  xmatdefs, matreal, usigmoide;

 type
  { TRecXCF
  Información de un punto
  }
  TRecXCF = class
    X: TVectR;
    Y: TVectR;
    constructor Create( dimX, dimY: integer );
    constructor Create_LoadFromFile(f: TStream);
    procedure StoreInFile( f: TStream );
    procedure Free;
    function Clonar: TRecXCF;

    function getCF: NReal;
    procedure setCF( aCF: NReal );

    // CF = Y.e(1)
    property CF: NReal read getCF write setCF;

    function filtrarMenorQue( umbral: NReal ): boolean;

    // retorna TRUE si ( Y(j) < Cota_Inf ) or ( Y(j) > Cota_Sup )
    function filtrarYEnRango( j: integer; cota_inf, cota_sup: NReal ): boolean;

    // retorna TRUE si ( Y(j) < Cota_Inf ) or ( Y(j) > Cota_Sup ) y además
    // modfica Y para obligarla a estar en el rango.
    // Se asume que Cota_inf < Cota_Sup
    function SaturarYEnRango( j: integer; cota_inf, cota_sup: NReal ): boolean;

    function serialize( sep: char= ',' ): string;

  end;


  TDAOfRecXCF = array of TRecXCF;

  TMatOfRecXCF = array of array of TRecXCF;

  { TCronicaXCF
  Información de una crónica de puntos
  }

  TCronicaXCF = class
    semilla: integer;
    XCF: TDAOfRecXCF;
    constructor Create( dimX, nPasosT, semilla: integer );
    constructor Create_LoadFromFile(f: TStream);
    procedure StoreInFile( f: TStream );
    procedure Free;
  end;

  { TRamilleteCronicasXCF

  Información de un conjunto de cróncias}

  TRamilleteCronicasXCF = class( TList )
    kiter: integer; // 1 ... número de iteración.
    constructor Create( kiter: integer );
    constructor Create_LoadFromFile(f: TStream);
    procedure StoreInFile( f: TStream );
    procedure Free;
  end;

  { TRamilletes
  Lista de ramilletes
  }

  TRamilletes = class( TList )
    constructor Create;
    constructor Create_LoadFromFile(f: TStream);
    procedure StoreInFile( f: TStream );
    procedure Free;
  end;


    // busca el costo mínimo y dc=(c_max - c_min) y escala todos los costos
    // c_i =  ( (c_i-c_min)/dc -0.5) * alfa + 0.5
    // Se supone que la dimensión de la salida es 1 (uno).
    procedure Normalizar_conjuntoXCF(
      out c_min, dc: NReal; var conj_XCF: TDAOfRecXCF; alfa: NReal );


    // busca parámetros de neuronas de normalización de las entradas para que
    // las mismas varíen entre -0.5 y 0.5 para que puedean ser usado como los páremtos
    // de una neurona y la señal quede centrada.
    // Tranforma los datos de forma que la serie es la de la salida de la neurona
    // cuyos parámetros se calculan.
    procedure Escalas_Entrada_j_conjuntoXCF(
       var ro_x, bias_x: NReal;
       jEntrada: integer;
       var conj_XCF: TDAOfRecXCF );



    // Crea y retorna lso vectroes xmin, xmax con los mínimos y máximos
    // del conjunto de datos de entrada.
    procedure CalcMinMax_Entradas_conjuntoXCF(
        out xmin, xmax: TVectR; const conj_XCF: TDAOfRecXCF);

    // Crea y retorna lso vectroes ymin, ymax con los mínimos y máximos
    // del conjunto de datos de entrada.
    procedure CalcMinMax_Salidas_conjuntoXCF(
        out ymin, ymax: TVectR; const conj_XCF: TDAOfRecXCF);


    // "mejor_neurona" retorna el vector de pesos de la neurona que mejor
    // aproxima el conjunto de puntos normalizado.
    // En el conjunto de puntos se retornan los residuos de la aproximación
    // como para ser usados en otra llamada al mismo método para calcular así
    // la siguiente "mejor_neurona"

    // El vector alfa se instancia dentro del procedimiento.
    // La neurona se calcula para aproximar CF_i por y_i donde:
    // y_i = sigmoide( alfa.escalar( X_i ) + xo )
    // El resultado de la función es la varianza de lo no explicado
    // calculada como: prom((CF_i - y_i)^2)

    function MejorNeurona(
      out alfa: TVectR; var x0: NReal;
      out c_min, dc: NReal;
      var conj_XCF: TDAOfRecXCF; var pesos: TVectR ): NReal;



function clonar_data_xcf( const data: TDAOfRecXCF ):  TDAOfRecXCF;
procedure free_data_xcf( var data: TDAOfRecXCF );

// Crea la etructura llena de ceros.
function Crear_data_xcf_0( dimX, nPuntos: integer ): TDAOfRecXCF;

// Crea un objeto del tipo TSeriesDeDatos a partir del conjunto de records datos.
// Considera los valores de X como las series de entrada y los de Y como las
// series de Salida.
function DAofRecXCF_To_SeriesDeDatos(
   const datos: TDAOfRecXCF;
   dtPrimeraMuestra: TDateTime = 0;
   PeriodoDeMuestreo_horas: NReal = 0 ): TSeriesDeDatos;

// Crea el conjunto de test del tipo CF(x) = exp(- (sum_k( k * x_k ) ) )
// dimX es la dimensión de X y nPuntos es la cantidad de muestras a generar
// tomando valores al azar de los x_k en el rango [0,1)
function Crear_XCF_Test01( dimX, nPuntos: integer; semilla: cardinal ): TDAOfRecXCF;

// Crea conjunto de test CF(x) = ( x < 0.5 )
function Crear_XCF_Test02_( nPuntos: integer; semilla: cardinal ): TDAOfRecXCF;


// Ordena los registros según el Y(j)
procedure SortDAOfRecXCFbyY(var data: TDAofRecXCF; j: integer; creciente: boolean );

// Cuenta la cantidad de muestras en cada fracción del rango de la salida j
// retorna el ymin y ymax (los extremos del rango) y la cuenta de muestras en
// cada fracción dy = (ymax - ymin)/N
function CuentaPorRango( out ymin, ymax: NReal; const data: TDAofRecXCF; N, j: integer ): TDAOfNInt;


implementation


procedure Normalizar_conjuntoXCF(
  out c_min, dc: NReal;
  var conj_XCF: TDAOfRecXCF; alfa: NReal);
var
  k: integer;
  aRec: TRecXCF;
  cmin, cmax, c: NReal;
begin
  aRec:= conj_XCF[0];
  cmin:= aRec.CF;
  cmax:= cmin;
  for k:= 1 to high( conj_XCF ) do
  begin
    c:= conj_XCF[k].Cf;
    if c < cmin then
      cmin:= c
    else if c > cmax then
      cmax:= c;
  end;
  c_min:= cmin;
  dc:= (cmax - cmin);
  if EsCero( dc ) then
    for k:= 0 to high( conj_XCF ) do
    begin
      aRec:= conj_XCF[k];
      aRec.CF:= aRec.CF -cmin;
    end
  else
    for k:= 0 to high( conj_XCF ) do
    begin
      aRec:= conj_XCF[k];
      aRec.Cf:= ((aRec.CF - cmin )/dc - 0.5 ) * alfa + 0.5;
    end;
end;



procedure Escalas_Entrada_j_conjuntoXCF(var ro_x, bias_x: NReal;
  jEntrada: integer; var conj_XCF: TDAOfRecXCF);

var
  k: integer;
  aRec: TRecXCF;
  xmin, xmax, x, y, dx: NReal;

begin
   aRec:= conj_XCF[0];
   xmin:= aRec.X.e(jEntrada);
   xmax:= xmin;
   for k:= 1 to high( conj_XCF ) do
   begin
     x:= conj_XCF[k].X.e(jEntrada);
     if x < xmin then
       xmin:= x
     else if x > xmax then
       xmax:= x;
   end;

   dx:= ( xmax - xmin);
   if abs( dx ) > 1e-10 then
   begin
     ro_x:= 1/dx;
     bias_x:= 0.5 - xmax/dx ;
   end
   else
   begin
     ro_x:= 1;
     bias_x:= -xmax;
   end;

   for k:= 0 to high( conj_XCF ) do
   begin
     x:= conj_XCF[k].X.e(jEntrada);
     y:= sigmoide( x * ro_x + bias_x );
     conj_XCF[k].X.pon_e(jEntrada, y );
   end;
end;

procedure CalcMinMax_Entradas_conjuntoXCF(out xmin, xmax: TVectR;
  const conj_XCF: TDAOfRecXCF);
var
  k, jEntrada: integer;
  aRec: TRecXCF;
  x, y, dx: NReal;

begin
  aRec:= conj_XCF[0];
  xmin:= TVectR.Create_init( aRec.X.n );
  xmax:= TVectR.Create_init( aRec.X.n );

  for jEntrada:= 1 to aRec.X.n do
  begin
   xmin.pon_e( jEntrada, aRec.X.e(jEntrada) );
   xmax.pon_e( jEntrada, aRec.X.e(jEntrada ) );
  end;

  for k:= 1 to high( conj_XCF ) do
  begin
    aRec:= conj_XCF[ k ];
    for jEntrada:= 1 to aRec.X.n do
    begin
      x:= aRec.X.e( jEntrada );
     if x < xmin.e( jEntrada ) then
       xmin.pon_e( jEntrada, x )
     else if x > xmax.e( jEntrada ) then
       xmax.pon_e( jEntrada, x );
    end;
  end;
end;

procedure CalcMinMax_Salidas_conjuntoXCF(out ymin, ymax: TVectR;
  const conj_XCF: TDAOfRecXCF);
var
  k, jSalida: integer;
  aRec: TRecXCF;
  y: NReal;

begin
  aRec:= conj_XCF[0];
  ymin:= TVectR.Create_init( aRec.Y.n );
  ymax:= TVectR.Create_init( aRec.Y.n );

  for jSalida:= 1 to aRec.Y.n do
  begin
   ymin.pon_e( jSalida, aRec.Y.e(jSalida) );
   ymax.pon_e( jSalida, aRec.Y.e(jSalida ) );
  end;

  for k:= 1 to high( conj_XCF ) do
  begin
    aRec:= conj_XCF[ k ];
    for jSalida:= 1 to aRec.Y.n do
    begin
      y:= aRec.Y.e( jSalida );
     if y < ymin.e( jSalida ) then
       ymin.pon_e( jSalida, y )
     else if y > ymax.e( jSalida ) then
       ymax.pon_e( jSalida, y );
    end;
  end;
end;


function MejorNeurona(out alfa: TVectR; var x0: NReal; out c_min, dc: NReal;
  var conj_XCF: TDAOfRecXCF; var pesos: TVectR): NReal;
var
  N: integer;
  res: NREal;
  MA, MB: TMatR;
  k, j, h: integer;
  z, ax: NReal;
  X: TVectR;
  NPuntos: integer;
  res_exp10: integer;
  res_invertible: boolean;
  cf: NReal;
  z_x: NReal;
  peso: NReal;
  cf_aprox: NReal;
  dev_sigmoide: NReal;

begin
  Normalizar_conjuntoXCF( c_min, dc, conj_XCF, 0.7 );
  res:= 0;
  N:= conj_XCF[0].X.n;
  alfa:= TVectR.Create_Init( N );
  NPuntos:= length( conj_XCF );

  if EsCero( dc ) then
  begin
    alfa.Ceros;
    x0:= inv_sigmoide( c_min );
    for k:= 0 to NPuntos-1 do
      conj_XCF[k].CF:= 0;
  end
  else
  begin

   // Armado de problema de mínimos cuadrados
   // z_i = alfa.escalar( X_i ) - xo
   MA:= TMatR.Create_Init( N+1, N+1 );
   MB:= TMatR.Create_init( N+1, 1 );
   for k:= 0 to NPuntos-1 do
   begin
      X:= conj_XCF[k].X;
      cf:= conj_XCF[k].CF;
      dev_sigmoide:= cf * ( 1-cf );
      peso:= dev_sigmoide* pesos.e( k+1 );
      if dev_sigmoide > 25.0E-08 then
      begin
        z:= inv_sigmoide( cf );
        for h:= 1 to N do
        begin
          ax:= X.pv[h]*peso;
          for j:= h to N do
            MA.acum_e( h, j, ax * X.pv[j] );
          MA.acum_e(h, N+1, ax );
          MB.acum_e(h, 1, ax * z );
        end;
        h:= N+1;
        ax:= peso;
        for j:= h to N do
          MA.acum_e( h, j, ax * X.pv[j] );
        MA.acum_e(h, N+1, ax );
        MB.acum_e(h, 1, ax * z );
      end;
   end;

   // Completo el triángulo inferior de MA
   for h:= 1 to N+1 do
     for j:= h+1 to N+1 do
       MA.pon_e( j, h , MA.e( h, j ));

   MA.Escaler( MB, res_invertible, res_exp10);
   if not res_invertible then
     raise Exception.Create('No Invertible MINCUAD en MejorNeurona ... hay que pensar ');

   for k:= 1 to Alfa.n do
     Alfa.pon_e( k, MB.e(k,1));
   x0:= MB.e( MB.nf, 1 );

   MB.Free;
   MA.Free;

   // Ahora calculamos los residuos
   for k:= 0 to NPuntos -1 do
   begin
    X:= conj_XCF[k].X;
    cf:=conj_XCF[k].CF;
    z:= inv_sigmoide( cf );
    z_x:= alfa.PEV( X ) - x0;
    conj_XCF[k].CF:= z - z_x; // guardamos el residuo para la próxima Neurona
    cf_aprox:= sigmoide( z_x );

    peso:= sqr(cf - cf_aprox );
    pesos.pon_e( k+1, peso );

    res:= res + peso; // Acumulamos el residuo de esta explicación para devolver
   end;
   res:= res / (NPuntos-1); //
  end;
  result:= res;
end;

function clonar_data_xcf(const data: TDAOfRecXCF): TDAOfRecXCF;
var
  res: TDAOfRecXCF;
  k: integer;
begin
  setlength( res, Length( data ) );
  for k:= 0 to high( res ) do
   res[k]:= data[k].Clonar;
  result:= res;
end;

procedure free_data_xcf(var data: TDAOfRecXCF);
var
  k: integer;
begin
  for k:= 0 to high( data ) do
   data[k].Free;
  setlength( data, 0 );
end;

function Crear_data_xcf_0(dimX, nPuntos: integer): TDAOfRecXCF;
var
  res: TDAofRecXCF;
  punto: integer;
begin
  setlength( res, nPuntos );
  for punto := 0 to nPuntos - 1 do
   res[punto]:= TRecXCF.Create( dimX, 1 );
  result:= res;
end;

function DAofRecXCF_To_SeriesDeDatos(const datos: TDAOfRecXCF;
  dtPrimeraMuestra: TDateTime; PeriodoDeMuestreo_horas: NReal): TSeriesDeDatos;
var
  res: TSeriesDeDatos;

  aRec: TRecXCF;
  k, j: integer;
  nEntradas, nSalidas: integer;

begin
  if ( datos = nil ) or ( length( datos ) = 0 ) then
  begin
    result:= nil;
    exit;
  end;

  res:= TSeriesDeDatos.CreateVacia( dtPrimeraMuestra, PeriodoDeMuestreo_horas, length( datos), 1 );
  aRec:= datos[0];
  nEntradas:= aRec.X.n;
  nSalidas:= aRec.Y.n;
  for k:= 1 to nEntradas do
    res.AddSerie( 'x_'+IntToStr( k ), nil, ENTRADA );
  for k:= 1 to nSalidas do
    res.AddSerie( 'y_'+IntToStr( k ), nil, SALIDA );

  for j:= 0 to high( datos ) do
  begin
   aRec:= datos[j];

   for k:= 0 to nEntradas-1 do
     res.series[k].pon_e(j+1, aRec.x.e( k+1) ) ;

   for k:= 0 to nSalidas-1 do
     res.series[nEntradas + k].pon_e(j+1, aRec.Y.e( k+1) ) ;

  end;

  result:= res;
end;


// Crea el conjunto de test del tipo CF(x) = exp(- (sum_k( k * x_k ) ) )
// dimX es la dimensión de X y nPuntos es la cantidad de muestras a generar
// tomando valores al azar de los x_k en el rango [0,1)
function Crear_XCF_Test01( dimX, nPuntos: integer; semilla: cardinal ): TDAOfRecXCF;
var
  res: TDAofRecXCF;
  k: integer;
  punto: integer;
  aRec: TRecXCF;
  alfa, x: NReal;
begin
  RandSeed:= semilla;
  setlength( res, nPuntos );
  alfa:= 0;
  for punto := 0 to nPuntos - 1 do
  begin
   aRec:= TRecXCF.Create( dimX, 1 );
    for k:= 1 to dimX  do
    begin
      x:= random;
      alfa:= alfa + k * x;
      aRec.X.pon_e( k, x );
    end;
    aRec.CF:= exp( -alfa );
    res[punto]:= aRec;
  end;
  result:= res;
end;


function Crear_XCF_Test02_( nPuntos: integer; semilla: cardinal ): TDAOfRecXCF;
var
  res: TDAofRecXCF;
  k: integer;
  punto: integer;
  aRec: TRecXCF;
  x: NReal;
begin
  RandSeed:= semilla;
  setlength( res, nPuntos );
  for punto := 0 to nPuntos - 1 do
  begin
    aRec:= TRecXCF.Create( 1, 1 );
    x:= ( punto/ nPuntos - 0.5 ) * 100.0;
    aRec.X.pon_e( 1, x );
    if x < 0.5 then
       aRec.CF:= 1.0
    else
       aRec.CF:= 0.0;

    res[punto]:= aRec;
  end;
  result:= res;
end;

procedure SortDAOfRecXCFbyY(var data: TDAofRecXCF; j: integer;
  creciente: boolean);

var
  v: TVectR;
  idx: TDAOfNInt;
  k: integer;
  tmp: TDAOfRecXCF;
begin
  setlength( idx, length( data ) +1 );
  setlength( tmp, length( data ) );
  v:= TVectR.Create_init( length( data ) );
  for k:= 0 to high( data )  do
  begin
    v.pon_e( k+1, data[k].Y.e(j) );
    idx[k+1]:= k;
    tmp[k]:= data[k];
  end;
  v.Sort_idx( creciente, idx );

  for k:= 0 to high( data ) do
    data[k]:= tmp[ idx[k+1] ];

  setlength( tmp, 0 );
  setlength( idx, 0 );
  v.Free;
end;

function CuentaPorRango(out ymin, ymax: NReal; const data: TDAofRecXCF; N,
  j: integer): TDAOfNInt;
var
  k: integer;
  a: TRecXCF;
  res: TDAOfNInt;
  dy: NReal;
  kRango: integer;
  y: NReal;
begin
  if length( data ) = 0 then
  begin
    ymin:= 0; ymax:= 1; result:= nil;
    exit;
  end;
  a:= data[0];
  ymin:= a.Y.e( j );
  ymax:= a.Y.e( j );
  for k:= 1 to high( data ) do
  begin
    a:= data[k];
    y:= a.Y.e(j );
    if y > ymax then
      ymax:= y
    else if y < ymin then
      ymin:= y
  end;
  setlength( res, N );
  dy:= ( ymax - ymin ) / N;
  if dy < AsumaCero then
  begin
    res[0]:= length( data );
    result:= res;
    exit;
  end;
  for k:= 0 to high( data ) do
  begin
    a:= data[k];
    y:= a.y.e(j);
    kRango := min( trunc(( y - ymin )/dy ), N-1 );
    inc( res[ kRango ] );
  end;
  result:= res;
end;



{ TRamilletes }

constructor TRamilletes.Create;
begin
  inherited Create;
end;

constructor TRamilletes.Create_LoadFromFile(f: TStream);
var
  n, k: integer;
  aRamillete: TRamilleteCronicasXCF;
begin
  f.Read( n, sizeOf(n));
  for k:= 0 to n-1 do
  begin
    aRamillete:= TRamilleteCronicasXCF.Create_LoadFromFile( f );
    add( aRamillete );
  end;
end;

procedure TRamilletes.StoreInFile(f: TStream);
var
  n, k: integer;
  aRamillete: TRamilleteCronicasXCF;
begin
  n:= count;
  f.Write( n, sizeof(n) );
  for k:= 0 to n-1 do
  begin
    aRamillete:= items[k];
    aRamillete.StoreInFile( f );
  end;
end;

procedure TRamilletes.Free;
var
  k: integer;
  aRamillete: TRamilleteCronicasXCF;
begin
  for k:= 0 to count -1 do
  begin
    aRamillete:= items[k];
    aRamillete.Free;
  end;
  inherited Free;
end;

{ TRamilleteCronicasXCF }

constructor TRamilleteCronicasXCF.Create(kiter: integer);
begin
  inherited Create;
  self.kiter:= kiter;
end;

constructor TRamilleteCronicasXCF.Create_LoadFromFile(f: TStream);
var
  n, k: integer;
  aCron: TCronicaXCF;
begin
  inherited Create;
  f.Write( kiter, sizeof(kiter) );
  n:= count;
  f.Write( n, sizeof(n) );
  for k:= 0 to n-1 do
  begin
    aCron:= TCronicaXCF.Create_LoadFromFile( f );
    add( aCron );
  end;
end;

procedure TRamilleteCronicasXCF.StoreInFile(f: TStream);
var
  n, k: integer;
  aCron: TCronicaXCF;
begin
  f.Write( kiter, sizeof(kiter) );
  n:= count;
  f.Write( n, sizeof(n) );
  for k:= 0 to n-1 do
  begin
    aCron:= items[k];
    aCron.StoreInFile( f );
  end;
end;

procedure TRamilleteCronicasXCF.Free;
var
  k: integer;
  aCronicaXCF: TCronicaXCF;
begin
  for k:= 0 to count -1 do
  begin
    aCronicaXCF:= items[k];
    aCronicaXCF.Free;
  end;
  inherited Free;
end;



{ TCronicaXCF }

constructor TCronicaXCF.Create(dimX, nPasosT, semilla: integer);
var
  k: integer;
begin
  inherited create;
  self.semilla:= semilla;
  setlength( XCF, nPasosT );
  for k:= 0 to High( XCF ) do
   XCF[k]:= TRecXCF.Create( dimX, 1 );
end;

constructor TCronicaXCF.Create_LoadFromFile(f: TStream);
var
  k: integer;
  nPasosT: integer;
begin
  inherited Create;
  f.Write( semilla, sizeof(semilla) );
  f.Write( nPasosT, sizeof(nPasosT) );
  setlength( XCF, nPasosT );
  for k:= 0 to High( XCF ) do
    XCF[k]:= TRecXCF.Create_LoadFromFile( f );
end;

procedure TCronicaXCF.StoreInFile(f: TStream);
var
  n: integer;
  k: integer;
begin
  f.Write( semilla, sizeof( semilla) );
  n:= length( XCF );
  f.Write( n, sizeof( n ) );
  for k:= 0 to high( XCF ) do
    XCF[k].StoreInFile( f );
end;

procedure TCronicaXCF.Free;
var
  k: integer;
begin
  for k:= 0 to High( XCF ) do
   XCF[k].Free;
  setlength( XCF, 0 );
  inherited create;
end;


{ TRecXCF }

constructor TRecXCF.Create(dimX, dimY: integer);
begin
  inherited Create;
  X:= TVectR.Create_init( dimX );
  Y:= TVectR.Create_init( dimY );
end;

constructor TRecXCF.Create_LoadFromFile(f: TStream);
begin
  inherited Create;
  X:= TVectR.CreateFromBinStream( f );
  Y:= TVectR.CreateFromBinStream( f );
end;

procedure TRecXCF.StoreInFile(f: TStream);
begin
  X.WriteToBinStream( f );
  Y.WriteToBinStream( f )
end;

procedure TRecXCF.Free;
begin
  X.Free;
  Y.Free;
  inherited free;
end;

function TRecXCF.Clonar: TRecXCF;
var
  res: TRecXCF;
begin
  res:= TRecXCF.Create( X.n, Y.n );
  res.X.Copy( X );
  res.Y.Copy( Y );
  result:= res;
end;

function TRecXCF.getCF: NReal;
begin
  result:= Y.e(1);
end;

procedure TRecXCF.setCF(aCF: NReal);
begin
  Y.pon_e( 1 , aCF );
end;

function TRecXCF.filtrarMenorQue(umbral: NReal): boolean;
var
  k: integer;
begin
  for k:= 1 to X.n do
    if ( X.e(k) < umbral )  then
    begin
      result:= true;
      exit;
    end;

  for k:= 1 to Y.n do
    if Y.e(k) < umbral then
    begin
      result:= true;
      exit;
    end;

  result:= false;
end;

function TRecXCF.filtrarYEnRango(j: integer; cota_inf, cota_sup: NReal
  ): boolean;
begin
  result:= ( Y.e( j ) < cota_inf ) or ( Y.e(j) > cota_sup );
end;

function TRecXCF.SaturarYEnRango(j: integer; cota_inf, cota_sup: NReal
  ): boolean;
var
  v: NReal;
begin
  v:= Y.e( j );
  if v < cota_inf then
  begin
    v:= cota_Inf;
    Y.pon_e(j, v );
    result:= true;
  end
  else if v > cota_sup then
  begin
    v:= cota_Sup;
    Y.pon_e( j, v );
    result:= true;
  end
  else
    result:= false;
end;

function TRecXCF.serialize(sep: char): string;
begin
  Result:= x.serialize( sep )+#9+ y.serialize( sep );
end;

end.

