/* 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: */