/* ASCEND modelling environment
Copyright (C) 1996-2007 Carnegie Mellon University
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*//** @file
The module permits sensitivity analysis in an ASCEND model. In your model
file, you need to create some additional arrays like so:
X[1..m] IS_A solver_var; (* outputs *)
U[1..n] IS_A solver_var; (* inputs *)
dx_du[1..m][1..n] IS_A solver_var; (* data space *)
You must then 'ARE_THE_SAME' your problem variables into the spaces in the
first two of the above arrays.
Then, run EXTERNAL do_sensitivity(SELF,U[1..n],X[1..m],dx_du[1..m][1..n]);
In the array dx_du[1..m][1..n], values of the corresponding sensitivity
derivatives will be calculated.
A test/example model is available in the models directory, called
sensitivity_test.a4c.
--- implementation notes ---
This file attempts to implement the extraction of dy_dx from
a system of equations. If one considers a black-box where x are
the input variables, u are input parameters, y are the output
variables, f(x,y,u) is the system of equations that will be solved
for given x and u, then one can extract the sensitivity information
of y wrt x.
One crude but simple way of doing this is to finite-difference the
given black box, i.e, perturb x, n_x times by delta x, resolve
the system of equations at the new value of x, and compute
dy/dx = (f(x_1) - f(x_2))/(x_1 - x_2).
This is known to be very expensive.
The solution that will be adopted here is to formulate the Jacobian J of
the system, (or the system is already solved, to grab the jacobian at
the solution. Develop the sensitivity matrix df/dx by exact numnerical
differentiation; this will be a n x n_x matrix. Compute the LU factors
for J, and to solve column by column to : LU dz/dx = df/dx. Here
z, represents all the internal variables to the system and the strictly
output variables y. In other words J = df/dz.
Given the solution of the above problem we then simply extract the rows
of dz/dx, that pertain to the y variables that we are interested in;
this will give us dy/dx.
@todo There are files in tcltk called Sensitivity.[ch]. Do we need them?
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SENSITIVITY_DEBUG
ASC_EXPORT int sensitivity_register(void);
ExtMethodRun do_sensitivity_eval;
ExtMethodRun do_sensitivity_eval_all;
/**
Build then presolve an instance
*/
slv_system_t sens_presolve(struct Instance *inst){
slv_system_t sys;
slv_parameters_t parameters;
const SlvFunctionsT *S;
#ifdef SENSITIVITY_DEBUG
struct var_variable **vp;
struct rel_relation **rp;
int len, ind;
char *tmp=NULL;
#endif
sys = system_build(inst);
if (sys==NULL) {
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed to build system.");
return NULL;
}
S = solver_engine_named("QRSlv");
if(!S){
ERROR_REPORTER_HERE(ASC_PROG_ERR,"QRSlv solver not found (required for sensitivity)");
return NULL;
}
slv_select_solver(sys,S->number);
slv_get_parameters(sys,¶meters);
parameters.partition = 0;
slv_set_parameters(sys,¶meters);
slv_presolve(sys);
#ifdef SENSITIVITY_DEBUG
vp = slv_get_solvers_var_list(sys);
len = slv_get_num_solvers_vars(sys);
for (ind=0 ; indn_rows == dof->n_cols &&
dof->n_rows == dof->structural_rank)) {
ERROR_REPORTER_HERE(ASC_PROG_ERR,"System is not square");
result = 1;
goto finish;
}
CONSOLE_DEBUG("Presolved, square");
/*
prepare the inputs list
*/
vlist = slv_get_solvers_var_list(sys);
branch = gl_fetch(arglist,2); /* input args */
ninputs = gl_length(branch);
inputs_ndx_list = ASC_NEW_ARRAY(int,ninputs);
num_vars = slv_get_num_solvers_vars(sys);
for (c=0;c= 0){
if (var_instance(vlist[ind]) == atominst) {
inputs_ndx_list[c] = var_sindex(vlist[ind]);
found = 1;
}
--ind;
}
if(!found){
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Unable to find sensitivity input variable");
result = 1;
goto finish;
}
}
CONSOLE_DEBUG("%d inputs",ninputs);
/*
prepare the outputs list
*/
branch = gl_fetch(arglist,3); /* output args */
noutputs = gl_length(branch);
outputs_ndx_list = ASC_NEW_ARRAY(int,noutputs);
for (c=0;cstructural_rank);
if(result){
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed to calculate LUFactorJacobian");
goto finish;
}
result = Compute_dy_dx_smart(sys, scratch_vector, dy_dx,
inputs_ndx_list, ninputs,
outputs_ndx_list, noutputs
);
linsolqr_remove_rhs(lqr_sys,scratch_vector);
if (result) {
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed Compute_dy_dx");
goto finish;
}
CONSOLE_DEBUG("Computed dy/dx");
/*
Write the results back to the partials array in the
instance tree
*/
offset = 0;
for (i=0;i
EXTERN sensitivity_anal_all(
this_instance,
u_old[1..n_inputs],
u_new[1..n_inputs],
step_length
);
The results will be witten to standard out.
This function is more expensive from a a memory point of view,
as we keep around a dense matrix n_outputs * n_inputs, but here
n_outputs may be *much* larger depending on problem size.
*/
int sensitivity_anal_all( struct Instance *inst, /* not used but will be */
struct gl_list_t *arglist,
real64 step_length
){
struct Instance *which_instance;
struct gl_list_t *branch2, *branch3;
dof_t *dof;
struct var_variable **inputs = NULL, **outputs = NULL;
int *inputs_ndx_list = NULL, *outputs_ndx_list = NULL;
DenseMatrix dy_dx;
struct var_variable **vp,**ptr;
slv_system_t sys = NULL;
long c;
int noutputs=0, ninputs;
var_filter_t vfilter;
struct var_variable **new_inputs = NULL; /* optional stuff for variable
* projection */
linsolqr_system_t lqr_sys; /* stuff for the linear system & matrix */
mtx_matrix_t mtx;
int32 capacity;
real64 *scratch_vector = NULL;
int result=0;
/* Ignore unused params */
(void)inst; (void)step_length;
/*
* Call the presolve for the system. This should number variables
* and relations as well create and order the main Jacobian. The
* only potential problem that I see here is that presolve using
* the slv0 solver *only* recognizes solver vars. So that if one
* wanted to see the sensitivity of a *parameter*, it would not
* be possible. We will have to trap this in CheckArgs.
*
* Also the current version of ascend is fucked in how the var module
* handles variables and their numbering through the interface ptr
* crap.
*/
(void)NumberFreeVars(NULL); /* used to re-init the system */
(void)NumberRels(NULL); /* used to re-init the system */
which_instance = FetchElement(arglist,1,1);
sys = sens_presolve(which_instance);
if (!sys) {
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failed to presolve");
result = 1;
goto finish;
}
dof = slv_get_dofdata(sys);
/*
* prepare the inputs list. We dont need the index of the new_inputs
* list. We will grab them later if necessary.
*/
branch2 = gl_fetch(arglist,2); /* input args -- old u_values */
branch3 = gl_fetch(arglist,3); /* input args -- new u_values */
ninputs = gl_length(branch2);
inputs = ASC_NEW_ARRAY(struct var_variable *,ninputs+1);
new_inputs = ASC_NEW_ARRAY(struct var_variable *,ninputs+1);
inputs_ndx_list = ASC_NEW_ARRAY(int,ninputs);
for (c=0;cstructural_rank);
if (result) {
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failure in LUFactorJacobian");
goto finish;
}
result = Compute_dy_dx_smart(sys, scratch_vector, dy_dx,
inputs_ndx_list, ninputs,
outputs_ndx_list, noutputs
);
linsolqr_remove_rhs(lqr_sys,scratch_vector);
if (result) {
ERROR_REPORTER_HERE(ASC_PROG_ERR,"Failure in Compute_dy_dx");
goto finish;
}
/*
* Do some analysis on the results, inclusive of
* writing them to someplace useful.
*/
if(DoDataAnalysis(inputs, outputs, ninputs, noutputs, dy_dx)){
result = 1;
}
/*
* Some experimental projection stuff -- not used now.
* if (DoProject_X(inputs, new_inputs, step_length,
* outputs, ninputs, noutputs, dy_dx))
* result = 1;
*/
finish:
if (inputs) ascfree((char *)inputs);
if (new_inputs) ascfree((char *)new_inputs);
if (inputs_ndx_list) ascfree((char *)inputs_ndx_list);
if (outputs) ascfree((char *)outputs);
if (outputs_ndx_list) ascfree((char *)outputs_ndx_list);
densematrix_destroy(dy_dx);;
if (scratch_vector) ascfree((char *)scratch_vector);
if (sys) system_destroy(sys);
return result;
}
#if 0
static int ComputeInverse(slv_system_t sys,
real64 *rhs)
{
linsolqr_system_t lqr_sys;
mtx_matrix_t mtx;
int capacity,order;
real64 *solution = NULL;
int j,k;
lqr_sys = slv_get_linsolqr_sys(sys); /* get the linear system */
mtx = linsolqr_get_matrix(lqr_sys); /* get the matrix */
capacity = mtx_capacity(mtx);
zero_vector(rhs,capacity); /* zero the rhs */
solution = ASC_NEW_ARRAY_CLEAR(real64,capacity);
order = mtx_order(mtx);
for (j=0;j solver_var.\n"
" 3. y: where y is an array of > solver_var.\n"
" 4. dy/dx: which dy_dx[1..n_y][1..n_x].\n\n"
"See also sensitivity_anal_all.";
/** @TODO document what 'u_new' is all about...? */
const char sensitivity_all_help[] =
"Analyse the sensitivity of *all* variables in the system with respect\n"
"to the specific set of inputs 'u'. Instead of returning values to a\n"
"a special array inside the model, the results are written to the\n"
"console. Usage example:\n\n"
"EXTERN sensitivity_anal_all(\n"
" this_instance,\n"
" u_old[1..n_inputs],\n"
" u_new[1..n_inputs],\n"
" step_length\n"
");\n\n"
"See also sensitivity_anal.";
int sensitivity_register(void){
int result=0;
result += CreateUserFunctionMethod("do_sensitivity",
do_sensitivity_eval,
4,sensitivity_help,NULL,NULL
);
result += CreateUserFunctionMethod("do_sensitivity_all",
do_sensitivity_eval_all,
4,sensitivity_all_help,NULL,NULL
);
return result;
}
/* :ex: set ts=4: */