//  -*- C++  -*-
//
//======================================================================
//  File:         dx.c
//  Author:       Thomas G. Dietterich
//  Description:  Implements dx classes for decision-theoretic diagnosis
//
//  Copyright (c) 1995 by Thomas G. Dietterich.  All Rights Reserved.
//       may be reproduced by any non-commercial purpose.
//======================================================================

#include "dx.h"
#include <fstream.h>

int DEBUG = 0;

//----------------------------------------------------------------------
//
//  dxInfo implementation
//

istream & operator >> (istream & str, dxInfo & info)
// read in a dxInfo record.  Move the repair cost of repair-only
// variables to the observationCost field and set repairCost to 0.
{
  int index;
  str >> index;  // ignore this item.
  str >> info.name >> info.isObservable >> info.observationCost >> info.isRepairable >>
    info.repairCost;
  if(!info.isObservable && info.isRepairable) {
    info.observationCost = info.repairCost;
    info.repairCost = 0;
  }
  return str;
}


//----------------------------------------------------------------------
//
// dxState implementation
//

dxState::dxState(const dxState & other): 
  nvars(other.nvars), state(other.nvars,-1), probability(other.nvars,0)
// copy constructor.  
{
  nvars = other.nvars;
  state = other.state;
  probability = other.probability;
  bestAction = other.bestAction;
}


float dxState::singleFaultProbability(dxProblem & problem, bayesNet & net, int var)
// compute the probability that variable var is faulty according to net.
{
  list<int> vars;
  list<int> values;
  vars.add(var);
  values.add(1);
  // for every other repairable component, assert that it is ok
  for (int i = 0; i < nvars; i++) {
    if(problem.variables[i].isRepairable && state[i] == -1 && i != var) {
      // assume it is not broken
      vars.add(i);
      values.add(0);
    }
  }
  return net.query(vars, values);
}


float dxState::value(dxProblem & problem, bayesNet & net)
// compute the value of this state, determines
// bestAction ObservationRepair or Repair action
// by sideeffect.  Does not consider pure
// observation actions.
{
  // this code assumes that the variables in problem have been
  // preprocessed so that repair-only variables have a repair cost of
  // 0 and an observationCost equal to their original repair cost.
  // we first compute the probability of failure of each
  // not-yet-repaired but repairable component.  We sum up these
  // probabilities and normalize them, thus imposing the single-fault
  // assumption. 
  float totalFaultProbability = 0.0;
  probability.fill(0);
  int i;
  for(i = 0; i < nvars; i++) {
    if (state[i] == -1 && problem.variables[i].isRepairable) {
      float pfail =  singleFaultProbability(problem, net, i);
      probability[i] = pfail;
      totalFaultProbability += pfail;
    }
  }
  // now go through the probability array and compute the ratios
  orderedList<varAndRatio> ratios;
  float bestRatio = 0.0;

  if (DEBUG) { cout << "  variable  probability of failure" << endl; }
  for(i = 0; i < nvars; i++) {
    if (state[i] == -1) {
      // if this variable is unknown and it is repairable
      if (problem.variables[i].isRepairable) {
	if(totalFaultProbability > 0) probability[i] /= totalFaultProbability;
	float pfail = probability[i];
	float ratio = (pfail / problem.variables[i].observationCost);
	ratios.add(varAndRatio(i, ratio));
	probability[i] = pfail;
	if (DEBUG) {
	  cout << "  " << problem.variables[i].name << "  \t" << pfail
	    << endl;
	}
	if (ratio > bestRatio) {
	  bestRatio = ratio;
	  bestAction = i;
	}
      }
    }
  }
  // the value of a state (assuming pure OR and R actions only)
  float answer = 0.0;
  float pfailSoFar = 0.0;  // probability of a failure in a previous component
  listIterator<varAndRatio> itr(ratios);
  for(itr.init(); !itr; ++itr) {
    i = itr().first;
    answer +=
      (1-pfailSoFar) * problem.variables[i].observationCost +
	probability[i] *
	  (problem.variables[i].repairCost +
	   problem.variables[problem.problemDefiningVariable].observationCost);
    pfailSoFar += probability[i];
  }
  return answer;
}

float dxState::lookaheadValue(dxProblem & problem, int var)
// what is the expected cost of a policy that observes variable var
// and then follows a repair-only policy thereafter?
{
  if (DEBUG) {
    cout << "Lookahead for observation of variable " << problem.variables[var].name << endl;
  }
  
  // CS533 students.  Your code goes here.

  float answer = 0.0;
  return answer;
}


//----------------------------------------------------------------------
//
// dxProblem implementation
//
dxProblem::dxProblem(char netFilename[], char problemFilename[]): variables(0)
{
  ifstream str(problemFilename);
  str >> *this;
  net = new bayesNet(netFilename);
  // check that the sizes match
  assert(variables.length() == net->size());
}

int dxProblem::solve()
// handle auxiliary observations
{
  dxState  currentState(variables.length());
  // assume that the device is currently broken
  currentState.state[problemDefiningVariable] = 1;  
  net->observe(problemDefiningVariable, 1);
  
  int cost = 0;
  // assume single fault
  while (1) {
    float bestCost = currentState.value(*this, *(this->net));  // compute value and best action
    int bestAction = currentState.bestAction;
    if (1) cout << "Expected value of repairing " <<
      variables[bestAction].name << " = " << bestCost << endl;

    // CS533: Your code here

    dxInfo & culprit = variables[bestAction];
    if(culprit.isObservable) {
      // ask user for result of observation
      cout << "Observe " << culprit.name << ".  What is result (" << 
	net->nodes[bestAction]->valueNames[0] << " = 0, " <<
	  net->nodes[bestAction]->valueNames[1] << " = 1)? ";
      int result;
      cin >> result;
      // update the Bayes net with this.  Note in the multiple fault
      // case, we should repair first if we can and then update the network.
      net->observe(bestAction, result);  
      currentState.state[bestAction] = result;
      cost += culprit.observationCost;

      // if the component is repairable, repair it
      // did we find the fault?
      if (culprit.isRepairable && result == 1) {
	cout << "Repair " << culprit.name << endl;
	cost += culprit.repairCost;
	cout << "Device should be working now!" << endl;;
	return cost;
      }
    }
    else {
      cout << "Repair " << culprit.name << endl;
      cost += culprit.observationCost;
      // update the state to reflect repair
      currentState.state[bestAction] = 0;  
      // update the network to reflect repair
      net->observe(bestAction, 0);
      // now observe problem defining variable
      cout << "Is the device working (ok = 0, broken = 1)? ";
      cost += variables[problemDefiningVariable].observationCost;
      int result;
      cin >> result;
      if(result == 0) return cost;
    }
  }
}


istream & operator >> (istream & str, dxProblem & problem)
{
  int nvars;
  str >> nvars >> problem.problemDefiningVariable;
  problem.variables.setSize(nvars);
  for(int i = 0; i < nvars; i++) {
    str >> problem.variables[i];
  }
  return str;
}


//----------------------------------------------------------------------
//
// varAndRatio implementation
//
//
int varAndRatio::operator < (varAndRatio & other)
{
  // biggest ratios come first (sort to the front)
  return this->second > other.second;
}
