/***************************************************************************

  	TITLE:		ls_trim.c

----------------------------------------------------------------------------

	FUNCTION:  	Trims the simulated aircraft by using certain
			controls to null out a similar number of outputs.

        This routine used modified Newton-Raphson method to find the vector
	of control corrections, delta_U, to drive a similar-sized vector of
	output errors, Y, to near-zero.  Nearness to zero is to within a
	tolerance specified by the Criteria vector.  An optional Weight
	vector can be used to improve the numerical properties of the
	Jacobian matrix (called H_Partials).

	Using a single-sided difference method, each control is
	independently perturbed and the change in each output of
	interest is calculated, forming a Jacobian matrix H (variable
	name is H_Partials):

			dY = H dU


	The columns of H correspond to the control effect; the rows of
	H correspond to the outputs affected.

	We wish to find dU such that for U = U0 + dU,
	
			Y = Y0 + dY = 0
			or dY = -Y0

	One solution is dU = inv(H)*(-Y0); however, inverting H
	directly is not numerically sound, since it may be singular
	(especially if one of the controls is on a limit, or the
	problem is poorly posed).  An alternative is to either weight
	the elements of dU to make them more normalized; another is to
	multiply both sides by the transpose of H and invert the
	resulting [H' H].  This routine does both:

                        -Y0  =      H dU
		     W (-Y0) =    W H dU	premultiply by W
		  H' W (-Y0) = H' W H dU        premultiply by H'

              dU = [inv(H' W H)][ H' W (-Y0)]   Solve for dU

	As a further refinement, dU is limited to a smallish magnitude
	so that Y approaches 0 in small steps (to avoid an overshoot
	if the problem is inherently non-linear).

	Lastly, this routine can be easily fooled by "local minima",
	or depressions in the solution space that don't lead to a Y =
	0 solution.  The only advice we can offer is to "go somewheres
	else and try again"; often approaching a trim solution from a
	different (non-trimmed) starting point will prove beneficial.


----------------------------------------------------------------------------

	MODULE STATUS:	developmental

----------------------------------------------------------------------------

	GENEALOGY:	Created from old CASTLE SHELL$TRIM.PAS
			on 6 FEB 95, which was based upon an Ames
			CASPRE routine called BQUIET.

----------------------------------------------------------------------------

	DESIGNED BY:	E. B. Jackson

	CODED BY:	same

	MAINTAINED BY:	same

----------------------------------------------------------------------------

	MODIFICATION HISTORY:

	DATE	PURPOSE						BY

	950307	Modified to make use of ls_get_sym_val and ls_put_sym_val
		routines.					EBJ
	950329	Fixed bug in making use of more than 3 controls;
		removed call by ls_trim_get_set() to ls_trim_init(). EBJ

	CURRENT RCS HEADER:

$Header: /var/cvs/FlightGear-0.9/source/src/FDM/LaRCsim/ls_trim.c,v 1.1.1.1 2002-09-10 01:14:02 curt Exp $
$Log: ls_trim.c,v $
Revision 1.1.1.1  2002-09-10 01:14:02  curt
Initial revision of FlightGear-0.9.0

Revision 1.2  2001/07/30 20:53:54  curt
Various MSVC tweaks and warning fixes.

Revision 1.1.1.1  1999/06/17 18:07:33  curt
Start of 0.7.x branch

Revision 1.1.1.1  1999/04/05 21:32:45  curt
Start of 0.6.x branch.

 * Revision 1.9  1995/03/29  16:09:56  bjax
 * Fixed bug in having more than three trim controls; removed unnecessary
 * call to ls_trim_init in ls_trim_get_set. EBJ
 *
 * Revision 1.8  1995/03/16  12:28:40  bjax
 * Fixed problem where ls_trim() returns non-zero if
 * symbols are not loaded - implies vehicle trimmed when
 * actually no trim attempt is made. This results in storing of non-
 * trimmed initial conditions in sims without defined trim controls.
 *
 * Revision 1.7  1995/03/15  12:17:12  bjax
 * Added flag marker line to ls_trim_put_set() routine output.
 *
 * Revision 1.6  1995/03/08  11:49:07  bjax
 * Minor improvements to ls_trim_get_set; deleted weighting parameter
 * for output definition; added comment lines to settings file output.
 *
 * Revision 1.5  1995/03/07  22:38:04  bjax
 * Removed ls_generic.h; this version relies entirely on symbol table routines to
 * set and get variable values. Added additional fields to Control record structure;
 * created Output record with appropriate fields. Added ls_trim_put_set() and
 * ls_trim_get_set() routines. Heavily modified initialization routine; most of this
 * logic now resides in ls_trim_get_set(). Renamed all routines so that they being
 * with "ls_trim_" to avoid conflicts.
 *  EBJ
 *
 * Revision 1.4  1995/03/07  13:04:16  bjax
 * Configured to use ls_get_sym_val() and ls_set_sym_val().
 *
 * Revision 1.3  1995/03/03  01:59:53  bjax
 * Moved definition of SYMBOL_NAME and SYMBOL_TYPE to ls_sym.h
 * and removed from this module. EBJ
 *
 * Revision 1.2  1995/02/27  19:53:41  bjax
 * Moved symbol routines to ls_sym.c to declutter this file. EBJ
 *
 * Revision 1.1  1995/02/27  18:14:10  bjax
 * Initial revision
 *

----------------------------------------------------------------------------

	REFERENCES:

----------------------------------------------------------------------------

	CALLED BY:

----------------------------------------------------------------------------

	CALLS TO:

----------------------------------------------------------------------------

	INPUTS:

----------------------------------------------------------------------------

	OUTPUTS:

--------------------------------------------------------------------------*/

static char rcsid[] = "$Id: ls_trim.c,v 1.1.1.1 2002-09-10 01:14:02 curt Exp $";

#ifdef __SUNPRO_CC
#  define _REENTRANT
#endif

#include <string.h>
#include "ls_constants.h"
#include "ls_types.h"
#include "ls_sym.h"
#include "ls_matrix.h"
#include "ls_interface.h"


#ifndef TRUE
#define FALSE  0
#define TRUE  !FALSE
#endif

#define MAX_NUMBER_OF_CONTROLS 10
#define MAX_NUMBER_OF_OUTPUTS 10
#define STEP_LIMIT 0.01
#define NIL_POINTER 0L

#define FACILITY_NAME_STRING "trim"
#define CURRENT_VERSION 10


typedef struct
{
    symbol_rec	Symbol;
    double	Min_Val, Max_Val, Curr_Val, Authority;
    double	Percent, Requested_Percent, Pert_Size;
    int		Ineffective, At_Limit;
} control_rec;

typedef struct
{
    symbol_rec	Symbol;
    double	Curr_Val, Weighting, Trim_Criteria;
    int		Uncontrollable;
} output_rec;


static	int		Symbols_loaded = 0;
static  int 		Index;
static  int 		Trim_Cycles;
static  int 		First;
static  int 		Trimmed;
static	double		Gain;

static  int		Number_of_Controls;
static	int		Number_of_Outputs;
static  control_rec	Controls[ MAX_NUMBER_OF_CONTROLS ];
static	output_rec	Outputs[ MAX_NUMBER_OF_OUTPUTS ];

static  double		**H_Partials;

static	double		Baseline_Output[ MAX_NUMBER_OF_OUTPUTS ];
static	double		Saved_Control, Saved_Control_Percent;

static  double		Cost, Previous_Cost;




int ls_trim_init()
/*  Initialize partials matrix */
{
    int i, error;
    // int result;

    Index = -1;
    Trim_Cycles = 0;
    Gain = 1;
    First = 1;
    Previous_Cost = 0.0;
    Trimmed = 0;

    for (i=0;i<Number_of_Controls;i++)
	{
	    Controls[i].Curr_Val = ls_get_sym_val( &Controls[i].Symbol, &error );
	    if (error) Controls[i].Symbol.Addr = NIL_POINTER;
	    Controls[i].Requested_Percent =
		(Controls[i].Curr_Val - Controls[i].Min_Val)
		/Controls[i].Authority;
	}

    H_Partials = nr_matrix( 1, Number_of_Controls, 1, Number_of_Controls );
    if (H_Partials == 0) return -1;

    return 0;
}

void ls_trim_get_vals()
/* Load the Output vector, and calculate control percent used */
{
    int i, error;

    for (i=0;i<Number_of_Outputs;i++)
	{
	    Outputs[i].Curr_Val = ls_get_sym_val( &Outputs[i].Symbol, &error );
	    if (error) Outputs[i].Symbol.Addr = NIL_POINTER;
	}

    Cost = 0.0;
    for (i=0;i<Number_of_Controls;i++)
	{
	    Controls[i].Curr_Val = ls_get_sym_val( &Controls[i].Symbol, &error );
	    if (error) Controls[i].Symbol.Addr = NIL_POINTER;
	    Controls[i].Percent =
		(Controls[i].Curr_Val - Controls[i].Min_Val)
		/Controls[i].Authority;
	}


}

void ls_trim_move_controls()
/* This routine moves the current control to specified percent of authority */
{
    int i;

    for(i=0;i<Number_of_Controls;i++)
	{
	    Controls[i].At_Limit = 0;
	    if (Controls[i].Requested_Percent <= 0.0)
		{
		    Controls[i].Requested_Percent = 0.0;
		    Controls[i].At_Limit = 1;
		}
	    if (Controls[i].Requested_Percent >= 1.0)
		{
		    Controls[i].Requested_Percent = 1.0;
		    Controls[i].At_Limit = 1;
		}
	    Controls[i].Curr_Val = Controls[i].Min_Val +
		(Controls[i].Max_Val - Controls[i].Min_Val) *
		Controls[i].Requested_Percent;
	}
}

void ls_trim_put_controls()
/* Put current control requests out to controls themselves */
{
    int i;

    for (i=0;i<Number_of_Controls;i++)
	    if (Controls[i].Symbol.Addr)
		ls_set_sym_val( &Controls[i].Symbol, Controls[i].Curr_Val );
}

void ls_trim_calc_cost()
/* This routine calculates the current distance, or cost, from trim */
{
    int i;

    Cost = 0.0;
    for(i=0;i<Number_of_Outputs;i++)
	Cost += pow((Outputs[i].Curr_Val/Outputs[i].Trim_Criteria),2.0);
}

void ls_trim_save_baseline_outputs()
{
    int i, error;

    for (i=0;i<Number_of_Outputs;i++)
	    Baseline_Output[i] = ls_get_sym_val( &Outputs[i].Symbol, &error );
}

int  ls_trim_eval_outputs()
{
    int i, trimmed;

    trimmed = 1;
    for(i=0;i<Number_of_Outputs;i++)
	if( fabs(Outputs[i].Curr_Val) > Outputs[i].Trim_Criteria) trimmed = 0;
    return trimmed;
}

void ls_trim_calc_h_column()
{
    int i;
    double delta_control, delta_output;

    delta_control = (Controls[Index].Curr_Val - Saved_Control)/Controls[Index].Authority;

    for(i=0;i<Number_of_Outputs;i++)
	{
	    delta_output = Outputs[i].Curr_Val - Baseline_Output[i];
	    H_Partials[i+1][Index+1] = delta_output/delta_control;
	}
}

void ls_trim_do_step()
{
    int i, j, l, singular;
    double 	**h_trans_w_h;
    double	delta_req_mag, scaling;
    double	delta_U_requested[ MAX_NUMBER_OF_CONTROLS ];
    double	temp[ MAX_NUMBER_OF_CONTROLS ];

    /* Identify ineffective controls (whose partials are all near zero) */

    for (j=0;j<Number_of_Controls;j++)
	{
	    Controls[j].Ineffective = 1;
	    for(i=0;i<Number_of_Outputs;i++)
		if (fabs(H_Partials[i+1][j+1]) > EPS) Controls[j].Ineffective = 0;
	}

    /* Identify uncontrollable outputs */

    for (j=0;j<Number_of_Outputs;j++)
	{
	    Outputs[j].Uncontrollable = 1;
	    for(i=0;i<Number_of_Controls;i++)
		if (fabs(H_Partials[j+1][i+1]) > EPS) Outputs[j].Uncontrollable = 0;
	}

    /* Calculate well-conditioned partials matrix [ H' W H ] */

    h_trans_w_h = nr_matrix(1, Number_of_Controls, 1, Number_of_Controls);
    if (h_trans_w_h == 0)
	{
	    fprintf(stderr, "Memory error in ls_trim().\n");
	    exit(1);
	}
    for (l=1;l<=Number_of_Controls;l++)
	for (j=1;j<=Number_of_Controls;j++)
	    {
		h_trans_w_h[l][j] = 0.0;
		for (i=1;i<=Number_of_Outputs;i++)
		    h_trans_w_h[l][j] +=
			H_Partials[i][l]*H_Partials[i][j]*Outputs[i-1].Weighting;
	    }

    /* Invert the partials array  [ inv( H' W H ) ]; note: h_trans_w_h is replaced
       with its inverse during this function call */

    singular = nr_gaussj( h_trans_w_h, Number_of_Controls, 0, 0 );

    if (singular) /* Can't invert successfully */
	{
	    nr_free_matrix( h_trans_w_h, 1, Number_of_Controls,
			                 1, Number_of_Controls );
	    fprintf(stderr, "Singular matrix in ls_trim().\n");
	    return;
	}

    /* Form right hand side of equality: temp = [ H' W (-Y) ] */

    for(i=0;i<Number_of_Controls;i++)
	{
	    temp[i] = 0.0;
	    for(j=0;j<Number_of_Outputs;j++)
		temp[i] -= H_Partials[j+1][i+1]*Baseline_Output[j]*Outputs[j].Weighting;
	}

    /* Solve for dU = [inv( H' W H )][ H' W (-Y)] */
    for(i=0;i<Number_of_Controls;i++)
	{
	    delta_U_requested[i] = 0.0;
	    for(j=0;j<Number_of_Controls;j++)
		delta_U_requested[i] += h_trans_w_h[i+1][j+1]*temp[j];
	}

    /* limit step magnitude to certain size, but not direction */

    delta_req_mag = 0.0;
    for(i=0;i<Number_of_Controls;i++)
	delta_req_mag += delta_U_requested[i]*delta_U_requested[i];
    delta_req_mag = sqrt(delta_req_mag);
    scaling = STEP_LIMIT/delta_req_mag;
    if (scaling < 1.0)
	for(i=0;i<Number_of_Controls;i++)
	    delta_U_requested[i] *= scaling;

    /* Convert deltas to percent of authority */

    for(i=0;i<Number_of_Controls;i++)
	Controls[i].Requested_Percent = Controls[i].Percent + delta_U_requested[i];

    /* free up temporary matrix */

    nr_free_matrix( h_trans_w_h, 1, Number_of_Controls,
		                 1, Number_of_Controls );

}



int ls_trim()
{
    const int Max_Cycles = 100;
    int Baseline;

    Trimmed = 0;
    if (Symbols_loaded) {

	ls_trim_init();    		/* Initialize Outputs & controls */
	ls_trim_get_vals();  /* Limit the current control settings */
	Baseline = TRUE;
	ls_trim_move_controls();		/* Write out the new values of controls */
	ls_trim_put_controls();
	ls_loop( 0.0, -1 );		/* Cycle the simulation once with new limited
					   controls */

	/* Main trim cycle loop follows */

	while((!Trimmed) && (Trim_Cycles < Max_Cycles))
	    {
		ls_trim_get_vals();
		if (Index == -1)
		    {
			ls_trim_calc_cost();
			/*Adjust_Gain();	*/
			ls_trim_save_baseline_outputs();
			Trimmed = ls_trim_eval_outputs();
		    }
		else
		    {
			ls_trim_calc_h_column();
			Controls[Index].Curr_Val = Saved_Control;
			Controls[Index].Percent  = Saved_Control_Percent;
			Controls[Index].Requested_Percent = Saved_Control_Percent;
		    }
		Index++;
		if (!Trimmed)
		    {
			if (Index >= Number_of_Controls)
			    {
				Baseline = TRUE;
				Index = -1;
				ls_trim_do_step();
			    }
			else
			    { /* Save the current value & pert next control */
				Baseline = FALSE;
				Saved_Control = Controls[Index].Curr_Val;
				Saved_Control_Percent = Controls[Index].Percent;

				if (Controls[Index].Percent < 
				    (1.0 - Controls[Index].Pert_Size) )
				    {
					Controls[Index].Requested_Percent =
					    Controls[Index].Percent +
					    Controls[Index].Pert_Size ;
				    }
				else
				    {
					Controls[Index].Requested_Percent =
					    Controls[Index].Percent -
					    Controls[Index].Pert_Size;
				    }
			    }
			ls_trim_move_controls();
			ls_trim_put_controls();
			ls_loop( 0.0, -1 );
			Trim_Cycles++;
		    }
	    }

	nr_free_matrix( H_Partials, 1, Number_of_Controls, 1, Number_of_Controls );
    }

    if (!Trimmed)  fprintf(stderr, "Trim unsuccessful.\n");
    return Trimmed;

}


char *ls_trim_get_set(char *buffer, char *eob)
/* This routine parses the settings file for "trim" entries. */
{

    static char *fac_name = FACILITY_NAME_STRING;
    char *bufptr, **lasts, *nullptr, null = '\0';
    char line[256];
    int n, ver, i, error, abrt;
    enum {controls_header, controls, outputs_header, outputs, done} looking_for;

    nullptr = &null;
    lasts = &nullptr;
    abrt = 0;
    looking_for = controls_header;


    n = sscanf(buffer, "%s", line);
    if (n == 0) return 0L;
    if (strncasecmp( fac_name, line, strlen(fac_name) )) return 0L;

    bufptr = strtok_r( buffer+strlen(fac_name)+1, "\n", lasts);
    if (bufptr == 0) return 0L;

    sscanf( bufptr, "%d", &ver );
    if (ver != CURRENT_VERSION) return 0L;

    while( !abrt && (eob > bufptr))
      {
	bufptr = strtok_r( 0L, "\n", lasts );
	if (bufptr == 0) return 0L;
	if (strncasecmp( bufptr, "end", 3) == 0) break;

	sscanf( bufptr, "%s", line );
	if (line[0] != '#') /* ignore comments */
	    {
		switch (looking_for)
		    {
		    case controls_header:
			{
			    if (strncasecmp( line, "controls", 8) == 0) 
				{
				    n = sscanf( bufptr, "%s%d", line, &Number_of_Controls );
				    if (n != 2) abrt = 1;
				    looking_for = controls;
				    i = 0;
				}
			    break;
			}
		    case controls:
			{
			    n = sscanf( bufptr, "%s%s%le%le%le", 
					Controls[i].Symbol.Mod_Name,
					Controls[i].Symbol.Par_Name,
					&Controls[i].Min_Val,
					&Controls[i].Max_Val,
					&Controls[i].Pert_Size); 
			    if (n != 5) abrt = 1;
			    Controls[i].Symbol.Addr = NIL_POINTER;
			    i++;
			    if (i >= Number_of_Controls) looking_for = outputs_header;
			    break;
			}
		    case outputs_header:
			{
			    if (strncasecmp( line, "outputs", 7) == 0) 
				{
				    n = sscanf( bufptr, "%s%d", line, &Number_of_Outputs );
				    if (n != 2) abrt = 1;
				    looking_for = outputs;
				    i = 0;
				}
			    break;
			}
		    case outputs:
			{
			    n = sscanf( bufptr, "%s%s%le", 
					Outputs[i].Symbol.Mod_Name,
					Outputs[i].Symbol.Par_Name,
					&Outputs[i].Trim_Criteria );
			    if (n != 3) abrt = 1;
			    Outputs[i].Symbol.Addr = NIL_POINTER;
			    i++;
			    if (i >= Number_of_Outputs) looking_for = done;
			}  
		    case done:
			{
			    break;
			}
		    }

	    }
      }

    if ((!abrt) && 
	(Number_of_Controls > 0) && 
	(Number_of_Outputs == Number_of_Controls))
	{
	    Symbols_loaded = 1;

	    for(i=0;i<Number_of_Controls;i++) /* Initialize fields in Controls data */
		{
		    Controls[i].Curr_Val = ls_get_sym_val( &Controls[i].Symbol, &error );
		    if (error) 
			Controls[i].Symbol.Addr = NIL_POINTER;
		    Controls[i].Authority = Controls[i].Max_Val - Controls[i].Min_Val;
		    if (Controls[i].Authority == 0.0) 
			Controls[i].Authority = 1.0;
		    Controls[i].Requested_Percent = 
			(Controls[i].Curr_Val - Controls[i].Min_Val)
			/Controls[i].Authority;
		    Controls[i].Pert_Size = Controls[i].Pert_Size/Controls[i].Authority;
		}

	    for (i=0;i<Number_of_Outputs;i++) /* Initialize fields in Outputs data */
		{
		    Outputs[i].Curr_Val = ls_get_sym_val( &Outputs[i].Symbol, &error );
		    if (error) Outputs[i].Symbol.Addr = NIL_POINTER;
		    Outputs[i].Weighting = 
			Outputs[0].Trim_Criteria/Outputs[i].Trim_Criteria;
		}
	}

    bufptr = *lasts;
    return bufptr;
}



void ls_trim_put_set( FILE *fp )
{
    int i;

    if (fp==0) return;
    fprintf(fp, "\n");
    fprintf(fp, "#==============================  %s\n", FACILITY_NAME_STRING);
    fprintf(fp, "\n");
    fprintf(fp, FACILITY_NAME_STRING);
    fprintf(fp, "\n");
    fprintf(fp, "%04d\n", CURRENT_VERSION);
    fprintf(fp, "  controls: %d\n", Number_of_Controls);
    fprintf(fp, "#    module    parameter   min_val   max_val   pert_size\n");
    for (i=0; i<Number_of_Controls; i++)
	fprintf(fp, "    %s\t%s\t%E\t%E\t%E\n", 
		Controls[i].Symbol.Mod_Name,
		Controls[i].Symbol.Par_Name,
		Controls[i].Min_Val,
		Controls[i].Max_Val,
		Controls[i].Pert_Size*Controls[i].Authority); 
    fprintf(fp, "  outputs: %d\n", Number_of_Outputs);
    fprintf(fp, "#    module    parameter   trim_criteria\n");
    for (i=0;i<Number_of_Outputs;i++)
	fprintf(fp, "    %s\t%s\t%E\n",
		Outputs[i].Symbol.Mod_Name,
		Outputs[i].Symbol.Par_Name,
		Outputs[i].Trim_Criteria );
    fprintf(fp, "end\n");
    return;
}
