/*
   File:        dyvcube.c
   Author:      Andrew W. Moore
   Created:     Wed Dec 30 06:33:12 EST 1998
   Description: AD-trees with real-valued outputs

   Copyright 2000, Schenley Park Research Inc
*/

#include "dcube.h"

void slow_check_dcube(dcube *dc)
{
  if ( dc != NULL )
  {
    if ( dc->size >= 0 )
    {
      int i;

      if ( dc->x != NULL )
	my_error("dcube with size >= 0 has non-NULL x field");

      for ( i = 0 ; i < dc->size ; i++ )
      {
	dcube *child = dc->children[i];
	if ( child != NULL )
	  slow_check_dcube(child);
      }
    }
    else
    {
      my_assert(dc->x != NULL);
      /* Put in temporarily while debugging sns, but other statvecs
         may allow a zero sum, so comment the following line out in that case..
      */
      my_assert(dyv_sum(dc->x) > 0.0);
    }
  }
}

void check_dcube_nonzero(dcube *dc)
{
  if ( dc == NULL )
  {
    /* skip */
  }
  else if ( dcube_is_leaf(dc) )
    my_assert(dyv_sum(dc->x) > 0.0);
  else
  {
    int i;
    for ( i = 0 ; i < dcube_num_children(dc) ; i++ )
      check_dcube_nonzero(dcube_child_ref(dc,i));
  }
}

dcube *mk_dcube_from_dyv(dyv *x)
{
  dcube *dc = AM_MALLOC(dcube);
  dc -> x = mk_copy_dyv(x);
  dc -> size = -1;
  dc -> children = NULL;
  check_dcube(dc);
  return dc;
}

dcube *mk_internal_dcube(int size)
{
  dcube *dc = AM_MALLOC(dcube);
  int i;
  dc->size = size;
  dc->x = NULL;
  dc->children = AM_MALLOC_ARRAY(dcube *,size);
  for ( i = 0 ; i < size ; i++ )
    dc->children[i] = NULL;
  check_dcube(dc);
  return(dc);
}

dcube *mk_zero_leaf_dcube(int dyv_size)
{
  dcube *dc = AM_MALLOC(dcube);
  dc->size = -1;
  dc->x = mk_zero_dyv(dyv_size);
  dc->children = NULL;
  return(dc);
}

dcube *mk_leaf_dcube(dyv *x)
{
  dcube *dc = mk_zero_leaf_dcube(dyv_size(x));
  copy_dyv(x,dc->x);
  check_dcube(dc);
  return(dc);
}

bool slow_dcube_is_leaf(dcube *dc)
{
  check_dcube(dc);
  return(dc->children == NULL);
}

int slow_dcube_num_children(dcube *dc)
{
  check_dcube(dc);
  if ( dcube_is_leaf(dc) )
    my_error("dcube_num_children: it's a leaf");
  return dc->size;
}

dcube *slow_dcube_child_ref(dcube *dc,int i)
{
  check_dcube(dc);
  if ( dcube_is_leaf(dc) || i < 0 || i >= dcube_num_children(dc) )
    my_error("dcube_child_ref");
  return dc->children[i];
}

void free_dcube_without_checking(dcube *dc)
{
  if ( dc != NULL )
  {
    if ( dc-> children == NULL )
    {
      my_assert(dc->x != NULL);
      free_dyv(dc->x);
    }
    else
    {
      int i;
      int num_children = dcube_num_children(dc);
      for ( i = 0 ; i < num_children ; i++ )
	free_dcube(dc->children[i]);
      AM_FREE_ARRAY(dc->children,dcube *,dc->size);
    }
    AM_FREE(dc,dcube);
  }
}

void free_dcube(dcube *dc)
{
  check_dcube(dc);
  free_dcube_without_checking(dc);
}

void fprintf_dcube(FILE *s,char *m1,dcube *dc,char *m2)
{
  check_dcube(dc);
  if ( dc == NULL )
    fprintf(s,"%s = NULL%s",m1,m2);
  else if ( dcube_is_leaf(dc) )
    fprintf_oneline_dyv(s,m1,dc->x,m2);
  else
  {
    int i;
    for ( i = 0 ; i < dcube_num_children(dc) ; i++ )
    {
      char *m = mk_printf("%s[%d]",m1,i);
      fprintf_dcube(s,m,dcube_child_ref(dc,i),m2);
      free_string(m);
    }
  }
}

void old_fprintf_dcube(FILE *s,char *m1,dcube *dc,char *m2)
{
  if ( dc == NULL )
    fprintf(s,"%s = NULL%s",m1,m2);
  else
  {
    char buff[1000];
    fprintf(s,"%s->size = %d%s",m1,dc->size,m2);
    if ( dcube_is_leaf(dc) )
    {
      sprintf(buff,"%s->x",m1);
      fprintf_dyv(s,buff,dc->x,m2);
    }
    else
    {
      int i;
      for ( i = 0 ; i < dc->size ; i++ )
      {
        sprintf(buff,"%s->children[%d]",m1,i);
        fprintf_dcube(s,buff,dc->children[i],m2);
      }
    }
  }
}

void pdcube(dcube *dc)
{
  fprintf_dcube(stdout,"dc",dc,"\n");
}

/* This works properly even if dc==NULL */
dcube *mk_copy_dcube(dcube *dc)
{
  dcube *newdc = NULL;
  check_dcube(dc);
  if ( dc == NULL )
  {
    /* skip */
  }
  else if ( dcube_is_leaf(dc) )
    newdc = mk_leaf_dcube(dc->x);
  else
  {
    int i;
    newdc = mk_internal_dcube(dcube_num_children(dc));
    for ( i = 0 ; i < dcube_num_children(dc) ; i++ )
      newdc->children[i] = mk_copy_dcube(dcube_child_ref(dc,i));
  }
  return newdc;
}

void dcube_scalar_mult(dcube *dc,double a)
{
  check_dcube(dc);
  if ( dc == NULL )
  {
    /* skip */
  }
  else if ( dcube_is_leaf(dc) )
    dyv_scalar_mult(dc->x,a,dc->x);
  else
  {
    int i;
    for ( i = 0 ; i < dcube_num_children(dc) ; i++ )
      dcube_scalar_mult(dcube_child_ref(dc,i),a);
  }
}

dcube *mk_dcube_scalar_mult(dcube *dc,double a)
{
  dcube *result;

  check_dcube(dc);

  if ( a == 0.0 )
    result = NULL;
  else
  {
    result = mk_copy_dcube(dc);
    dcube_scalar_mult(result,a);
  }
  return result;
}

bool all_zeroes(dyv *x)
{
  bool result = TRUE;
  int i;

  for ( i = 0 ; result && i < dyv_size(x) ; i++ )
    result = dyv_ref(x,i) == 0.0;

  return result;
}

/* Adds or Subtracts y from *x. *x is changed, y is unchanged.
   PRE: at all levels, arities of x and y must match. 
        All fields in all results must be non-negative.

   Returns a CANONICAL dcube in that there will be no leaf nodes
   that have all x of zero and no internal nodes with all
   children NULL.

   If x and y are equal, frees *x and sets *x == NULL.
*/
void dcube_add_or_subtract(dcube **x,dcube *y,bool add)
{
  if ( y == NULL )
  {
    /* skip */
  }
  else if ( *x == NULL )
  {
    check_dcube(y);
    if ( add )
      *x = mk_copy_dcube(y);
    else
      *x = mk_dcube_scalar_mult(y,-1.0);
  }
  else if ( dcube_is_leaf(*x) )
  {
    if ( !dcube_is_leaf(y) )
      my_error("dcube_subtract x is leaf y isnt");

    if ( add )
      dyv_plus((*x)->x,y->x,(*x)->x);
    else
      dyv_subtract((*x)->x,y->x,(*x)->x);

    if ( all_zeroes((*x)->x) )
    {
      free_dcube_without_checking(*x);
      *x = NULL;
    }
  }
  else
  {
    if ( dcube_is_leaf(y) )
      my_error("dcube_subtract x isnt leaf y is");
    else if ( (*x)->size != y->size )
      my_error("dcube_subtract size mismatch");
    else
    {
      int i;
      bool someone_is_non_zero = FALSE;
      for ( i = 0 ; i < (*x)->size ; i++ )
      {
        dcube_add_or_subtract(&((*x)->children[i]),y->children[i],add);
        if ( (*x)->children[i] != NULL )
          someone_is_non_zero = TRUE;
      }
      if ( !someone_is_non_zero )
      {
        free_dcube(*x);
        *x = NULL;
      }
    }
  }

  if ( *x != NULL ) check_dcube(*x);

}

/* Subtracts y from *x. *x is changed, y is unchanged.
   PRE: at all levels, arities of x and y must match. 
        All fields in all results must be non-negative.

   Returns a CANONICAL dcube in that there will be no leaf nodes
   that have all x of zero and no internal nodes with all
   children NULL.

   If x and y are equal, frees *x and sets *x == NULL.
*/
void dcube_subtract(dcube **x,dcube *y)
{
  dcube_add_or_subtract(x,y,FALSE);
}

/* Adds y to *x. *x is changed, y is unchanged.
   PRE: at all levels, arities of x and y must match. 
        All fields in all results must be non-negative.

   Returns a CANONICAL dcube in that there will be no leaf nodes
   that have all x of zero and no internal nodes with all
   children NULL.
*/
void dcube_add(dcube **x,dcube *y)
{
  dcube_add_or_subtract(x,y,TRUE);
}

int dyv_array_first_dyv_size(dyv_array *da)
{
  return dyv_size(dyv_array_ref(da,0));
}

bool dcube_is_zero(dcube *dc)
{
  bool result = TRUE;

  if ( dcube_is_leaf(dc) )
    result = all_zeroes(dc->x);
  else
  {    
    int i;
    for ( i = 0 ; i < dc->size && result ; i++ )
      result = dc->children[i] == NULL ||
               dcube_is_zero(dc->children[i]);
  }
  fprintf_dcube(stdout,"dcube_is_zero",dc,"\n");
  printf("Above dcube is zero = %d\n",result);
  wait_for_key();

  return(result);
}

bool dcube_contains_zero(dcube *dc)
{
  bool result = dcube_is_zero(dc);
  if ( !result && !dcube_is_leaf(dc) )
  {
    int i;
    for ( i = 0 ; i < dc->size && !result ; i++ )
      if ( dc->children[i] != NULL )
        result = dcube_contains_zero(dc->children[i]);
  }
  return(result);
}

void add_to_dcube_explain_info(dcube *dc,
			       int depth,
			       ivec *base_values,
			       ivec_array *values,
			       dyv_array *x)
{
  if ( dc == NULL )
  {
    /* skip */
  }
  else if ( dcube_is_leaf(dc) )
  {
    if ( depth != ivec_size(base_values) )
      my_error("lscjkcbkjcbkjdc");
    add_to_ivec_array(values,base_values);
    add_to_dyv_array(x,dc->x);
  }
  else
  {
    int i;
    for ( i = 0 ; i < dc->size ; i++ )
    {
      ivec_set(base_values,depth,i);
      add_to_dcube_explain_info(dc->children[i],depth+1,base_values,values,x);
      ivec_set(base_values,depth,-1);
    }
  }
}
    
/* values[i] are the values in the ith row of a compressed
   truth table represented by the dcube. x[i] is the
   aggregate_x matching the given values. */
void make_dcube_explain_info(dcube *dc,
                            int dcube_num_atts,
                            ivec_array **values,
                            dyv_array **x)
{
  ivec *base_values = mk_constant_ivec(dcube_num_atts,-1);
  *values = mk_empty_ivec_array();
  *x = mk_empty_dyv_array();
  add_to_dcube_explain_info(dc,0,base_values,*values,*x);
  free_ivec(base_values);
}

/* Returns number of non-zero cells in dcube */
int dcube_num_entries(dcube *dc)
{
  int result;

  if ( dc == NULL )
    result = 0;
  else if ( dcube_is_leaf(dc) )
    result = 1;
  else
  {
    int i;
    result = 0;
    for ( i = 0 ; i < dc->size ; i++ )
      result += dcube_num_entries(dc->children[i]);
  }

  return(result);
}
                
int dcube_max_count(dcube *dc)
{
  int result = 0;

  if ( dc == NULL )
  {
    /* skip */
  }
  else if ( dcube_is_leaf(dc) )
    result = (int) dyv_ref(dc->x,0);
  else
  {
    int i;
    for ( i = 0 ; i < dcube_num_children(dc) ; i++ )
      result = int_max(result,dcube_max_count(dcube_child_ref(dc,i)));
  }

  return result;
}

/* Treating values as a set of indexes into the K-dimensional
   array represented by dc, this function returns the sumvec
   x corresponding to the x stored in the specified array cell.

   If the values in x are all zero, returns NULL instead of a
   vector of zeroes. 

   PRE: values is exactly the same length as the number of dimensions
        of the dcube.
*/
dyv *dcube_values_to_x(dcube *dc,ivec *values)
{
  int i;

  for ( i = 0 ; dc != NULL && i < ivec_size(values) ; i++ )
    dc = dcube_child_ref(dc,ivec_ref(values,i));

  my_assert(dc==NULL || dcube_is_leaf(dc));

  return ( dc==NULL ) ? NULL : dc->x;
}

/* As above but just returns the count of the sumvec (i.e.
   the first array element of x) */
int dcube_values_to_count(dcube *dc,ivec *values)
{
  dyv *x = dcube_values_to_x(dc,values);
  return (x == NULL) ? 0 : (int) dyv_ref(x,0);
}

char *mk_explain_dcube_seps(sa_array *dim_to_valnames,
			    string_array *statvec_names)
{
  int num_dims = sa_array_size(dim_to_valnames);
  int statvec_size = string_array_size(statvec_names);
  int seps_num_bytes = num_dims + statvec_size + 3;
  char *seps = AM_MALLOC_ARRAY(char,seps_num_bytes);
  int i;

  for ( i = 0 ; i < seps_num_bytes-1 ; i++ )
    seps[i] = ' ';
  seps[0] = '|';
  seps[num_dims] = '|';
  seps[seps_num_bytes-3] = '|';
  seps[seps_num_bytes-2] = '|';
  seps[seps_num_bytes-1] = '\0';

  return seps;
}

string_matrix *mk_explain_dcube_string_matrix(dcube *dc,
					      string_array *dim_to_name,
					      sa_array *dim_to_valnames,
					      string_array *statvec_names,
					      ivec_array *entry_to_values,
					      dyv_array *entry_to_statvec)
{
  int num_dims = sa_array_size(dim_to_valnames);
  int entry,dim,statvec_comp;
  int statvec_size = string_array_size(statvec_names);
  string_matrix *sm = mk_string_matrix(0,num_dims + statvec_size+1);
  int row = sm_rows(sm);
  int num_entries = ivec_array_size(entry_to_values);

  sm_set_string(sm,row,0,"-");
  row = sm_rows(sm);

  for ( dim = 0 ; dim < num_dims ; dim++ )
    sm_set_string(sm,row,dim,string_array_ref(dim_to_name,dim));

  for ( statvec_comp = 0 ; statvec_comp < statvec_size ; statvec_comp++ )
    sm_set_string(sm,row,num_dims+statvec_comp,
		  string_array_ref(statvec_names,statvec_comp));

  row = sm_rows(sm);
  sm_set_string(sm,row,0,"-");

  for ( entry = 0 ; entry < num_entries ; entry++ )
  {
    ivec *dim_to_value = ivec_array_ref(entry_to_values,entry);
    dyv *statvec_comp_to_value = dyv_array_ref(entry_to_statvec,entry);

    my_assert(num_dims == ivec_size(dim_to_value));
    my_assert(statvec_size == dyv_size(statvec_comp_to_value));

    row = sm_rows(sm);

    for ( dim = 0 ; dim < num_dims ; dim++ )
    {
      string_array *dim_valnames = sa_array_ref(dim_to_valnames,dim);
      int value = ivec_ref(dim_to_value,dim);
      char *valname = string_array_ref(dim_valnames,value);
      sm_set_string(sm,row,dim,valname);
    }

    for ( statvec_comp = 0 ; statvec_comp < statvec_size ; statvec_comp++ )
    {
      double value = dyv_ref(statvec_comp_to_value,statvec_comp);
      sm_set_double(sm,row,num_dims+statvec_comp,value);
    }

    if ( dyv_sum(statvec_comp_to_value) == dyv_max(statvec_comp_to_value) )
      sm_set_string(sm,row,num_dims+statvec_size,"*");
  }

  row = sm_rows(sm);
  sm_set_string(sm,row,0,"-");

  return sm;
}

void explain_dcube_basic(dcube *dc,string_array *dim_to_name,
			 sa_array *dim_to_valnames,string_array *statvec_names,
			 ivec_array *entry_to_values,
			 dyv_array *entry_to_statvec)
{
  string_matrix *mk_explain_dcube_string_matrix(dcube *dc,
						string_array *dim_to_name,
						sa_array *dim_to_valnames,
						string_array *statvec_names,
						ivec_array *entry_to_values,
						dyv_array *entry_to_statvec);
  char *mk_explain_dcube_seps(sa_array *dim_to_valnames,
			      string_array *statvec_names);

  string_matrix *sm = 
    mk_explain_dcube_string_matrix(dc,dim_to_name,dim_to_valnames,
				   statvec_names,entry_to_values,
				   entry_to_statvec);

  char *seps = mk_explain_dcube_seps(dim_to_valnames,statvec_names);

  render_string_matrix_with_seps_and_dashes(stdout,sm,seps);

  free_string(seps);
  free_string_matrix(sm);
}

void explain_dcube(dcube *dc,string_array *dim_to_name,
		   sa_array *dim_to_valnames,string_array *statvec_names)
{
  ivec_array *entry_to_values;
  dyv_array *entry_to_statvec;
  
  make_dcube_explain_info(dc,string_array_size(dim_to_name),
			  &entry_to_values,&entry_to_statvec);

  explain_dcube_basic(dc,dim_to_name,dim_to_valnames,statvec_names,
		      entry_to_values,entry_to_statvec);

  free_ivec_array(entry_to_values);
  free_dyv_array(entry_to_statvec);
}

/* Returns -1 if dc has no leaf nodes */
int dcube_num_dims(dcube *dc)
{
  int result;

  if ( dc == NULL )
    result = -1;
  else if ( dcube_is_leaf(dc) )
    result = 0;
  else
  {
    int num_children = dcube_num_children(dc);
    int i;

    result = -1;
    for ( i = 0 ; result < 0 && i < num_children ; i++ )
    {
      int num_sub_dims = dcube_num_dims(dcube_child_ref(dc,i));
      if ( num_sub_dims >= 0 )
	result = num_sub_dims + 1;
    }
  }

  return result;
}

/* Returns -1 if dc has no leaf nodes */
int dcube_statvec_size(dcube *dc)
{
  int result;

  if ( dc == NULL )
    result = -1;
  else if ( dcube_is_leaf(dc) )
    result = dyv_size(dc->x);
  else
  {
    int num_children = dcube_num_children(dc);
    int i;

    result = -1;
    for ( i = 0 ; result < 0 && i < num_children ; i++ )
      result = dcube_statvec_size(dcube_child_ref(dc,i));
  }

  return result;
}

bool d2a_search(dcube *dc,ivec *d2a)
{
  bool result;
#ifdef DOING_ASSERTS
  int entry_size = ivec_size(d2a);
#endif

  if ( dc == NULL )
    result = FALSE;
  else if ( dcube_is_leaf(dc) )
    result = TRUE;
  else
  {
    int num_children = dcube_num_children(dc);
    int i;
    add_to_ivec(d2a,num_children);

    result = FALSE;
    for ( i = 0 ; !result && i < num_children ; i++ )
      result = d2a_search(dcube_child_ref(dc,i),d2a);

    if ( !result )
      ivec_remove_last_element(d2a);
  }

  my_assert(result || ivec_size(d2a) == entry_size);

  return result;
}

ivec *mk_dcube_dim_to_arity(dcube *dc)
{
  ivec *d2a = mk_ivec(0);
  bool ok = d2a_search(dc,d2a);
  my_assert_always(ok);
  my_assert(ivec_size(d2a) == dcube_num_dims(dc));
  return d2a;
}

sa_array *mk_valnames_from_arities(ivec *dim_to_arity)
{
  sa_array *valnames = mk_empty_sa_array();
  int i;
  for ( i = 0 ; i < ivec_size(dim_to_arity) ; i++ )
  {
    int arity = ivec_ref(dim_to_arity,i);
    string_array *sa = mk_int_string_array("",arity);
    add_to_sa_array(valnames,sa);
    free_string_array(sa);
  }
  return valnames;
}

void explain_dcube_simple(dcube *dc)
{
  string_array *dim_to_name = mk_int_string_array("dim",dcube_num_dims(dc));
  ivec *dim_to_arity = mk_dcube_dim_to_arity(dc);
  sa_array *dim_to_valnames = mk_valnames_from_arities(dim_to_arity);
  string_array *statvec_names = 
    mk_int_string_array("sv",dcube_statvec_size(dc));
  
  explain_dcube(dc,dim_to_name,dim_to_valnames,statvec_names);

  free_string_array(dim_to_name);
  free_ivec(dim_to_arity);
  free_sa_array(dim_to_valnames);
  free_string_array(statvec_names);
}

int dyv_num_non_zero_entries(dyv *x)
{
  int i;
  int result = 0;
  for ( i = 0 ; i < dyv_size(x) ; i++ )
  {
    if ( dyv_ref(x,i) != 0.0 )
      result += 1;
  }
  return result;
}

int dcube_num_non_zero_statvec_entries(dcube *dc)
{
  int result;
  if ( dcube_is_leaf(dc) )
    result = dyv_num_non_zero_entries(dc->x);
  else
  {
    int i;
    result = 0;

    for ( i = 0 ; i < dcube_num_children(dc) ; i++ )
    {
      dcube *child = dcube_child_ref(dc,i);
      if ( child != NULL )
	result += dcube_num_non_zero_statvec_entries(child);
    }
  }

  return result;
}

/* Scales statvec so that it has a sum of one. In other words,
   all components of sv are multiplied by 1/dyv_sum(sv).

   Prior to the linear transform to cause a sum to one, a
   constant (sumone_epsilon) is added to each element of the
   statvec */
void sumone_dyv(dyv *x,double sumone_epsilon)
{
  double sum;
  my_assert(dyv_min(x) >= 0.0);
  dyv_scalar_add(x,sumone_epsilon,x);
  sum = dyv_sum(x);
  my_assert(sum > 0.0);
  dyv_scalar_mult(x,1.0/sum,x);
}

/* Scales each statvec in dc individually so that each statvec
   a sum of one. In other word, if sv is a statvec in dc, then
   all components of sv are multiplied by 1/dyv_sum(sv).

   Prior to the linear transform to cause a sum to one, a
   constant (sumone_epsilon) is added to each element of the
   statvec */
void sumone_dcube_statvecs(dcube *dc,double sumone_epsilon)
{
  if ( dcube_is_leaf(dc) )
    sumone_dyv(dc->x,sumone_epsilon);
  else
  {
    int i;
    for ( i = 0 ; i < dcube_num_children(dc) ; i++ )
    {
      dcube *child = dcube_child_ref(dc,i);
      if ( child != NULL )
	sumone_dcube_statvecs(child,sumone_epsilon);
    }
  }
}

void dcube_sum_statvecs_helper(dcube *dc,dyv **r_sum)
{
  if ( dcube_is_leaf(dc) )
  {
    if ( *r_sum == NULL )
      *r_sum = mk_copy_dyv(dc->x);
    else
      dyv_plus(*r_sum,dc->x,*r_sum);
  }
  else
  {
    int i;
    for ( i = 0 ; i < dcube_num_children(dc) ; i++ )
    {
      dcube *child = dcube_child_ref(dc,i);
      if ( child != NULL )
	dcube_sum_statvecs_helper(child,r_sum);
    }
  }
}

/* Returns the sum of all the statvecs owned by dc.
   PRE: dc is non-NULL and dc contains at least one non-null statvec */
dyv *mk_dcube_sum_statvecs(dcube *dc)
{
  dyv *result = NULL;
  dcube_sum_statvecs_helper(dc,&result);

  if ( result == NULL )
    my_error("mk_dcube_sum_statvecs: dc should contain at least one non-NULL");

  return result;
}

/* NULL rows means use them all */
/* PRE: There must be at least one row in the sum */
dyv *mk_aggregate_x_from_row_to_x_subset(dyv_array *row_to_x,ivec *rows)
{
  dyv *result = NULL;
  int num_rows = (rows == NULL) ? dyv_array_size(row_to_x) : ivec_size(rows);

  if ( num_rows == 0 )
    my_error("mk_aggregate_x_from_row_to_x_subset: can't happen");
  else
  {
    int row_0 = (rows == NULL) ? 0 : ivec_ref(rows,0);
    int i;

    result = mk_copy_dyv(dyv_array_ref(row_to_x,row_0));

    for (i = 1 ; i < num_rows ; i++ )
    {
      int row_i = (rows == NULL) ? i : ivec_ref(rows,i);
      dyv *x_i = dyv_array_ref(row_to_x,row_i);
      dyv_plus(result,x_i,result);
    }
  }

  return result;
}
