コード例 #1
0
        /// <summary>
        /// Solves Markov Decision Model (MDM)
        /// </summary>
        /// <param name="numYears">Planning horizon for the optimal policy (number of years to look ahead for)</param>
        /// <param name="numIter">out Number of iterations run</param>
        /// <param name="errorMessage">out Error message</param>
        /// <param name="failCostOverridden">out True if failure cost was overridden</param>
        /// <param name="failureCost">out Estimated or overriding value of the failure cost</param>
        /// <returns>True on success, False on failure</returns>
        public Boolean Solve(Int32 numYears, out Int32 numIter, out Boolean failCostOverridden, out Double failureCost, out String errorMessage)
        {
            Boolean ok = true;

            numIter            = 0;
            errorMessage       = null;
            failCostOverridden = false;
            failureCost        = FailureCost;

            Log.InfoFormat("Solving Weibull-Markov Decision model - started. Planning horizon: {0}", numYears);

            try
            {
                Log.Info("Resetting.");

                _discFactor = 1.0 / (1.0 + DiscRate / 100.0);

                Log.InfoFormat("Discounting factor: {0}", _discFactor);

                FailureCostUsedInPolicy = null;

                ResetSolution();
                Int32   NS       = States.Count;
                Boolean noChange = true;

                Log.InfoFormat("Checking inputs");

                // Checking inputs
                for (Int32 i = NS - 1; i >= 0; i--)
                {
                    WeibullMarkovConditionState state = States[i];

                    if (state == null)
                    {
                        String err = String.Format("Slot number {0} (zero-based) in the condition state list is null or empty.", i);
                        throw new Exception(err);
                    }

                    if (i == NS - 1 && (state.Actions == null || state.Actions.Count < 1))
                    {
                        String err = String.Format("Last condition state (state number {0}) has no actions specified for it other than Do Nothing.", state.Number);
                        throw new Exception(err);
                    }

                    if (state.Beta == 1.0 || state.ff == 0.0)
                    {
                        Double f = Weibull.OneYearFailure(state.Eta, state.Beta, 1.0);
                        if (f <= 0.0)
                        {
                            String err = String.Format("No optimal solution can be found for condition state {0} because its failure probability is constant over time and equals zero or remains negligibly small.", state.Number);
                            throw new Exception(err);
                        }
                    }
                }

                Log.InfoFormat("Main iteration cycle - started");
                Log.InfoFormat("Nummber of condition states: {0}", NS);

                do
                {
                    noChange = true;
                    numIter++;

                    Log.InfoFormat("Iteration: {0}", numIter);

                    for (Int32 i = NS - 1; i >= 0; i--)
                    {
                        WeibullMarkovConditionState state = States[i];

                        Log.InfoFormat("Iter: {0}\tCondition state: {1}\t{2}\tEta={3}\tBeta={4}\tdfdt({5})={6}",
                                       numIter, state.Number, (i == NS - 1) ? "(last state)" : String.Empty, state.Eta, state.Beta, state.T, state.ff);

                        Log.InfoFormat("Iter: {0}\tCondition state: {1}\tNumber of actions: {2}", numIter, state.Number, state.Actions == null ? 0 : state.Actions.Count);

                        Int32  minCostActionNumber = -1;
                        Double minActionShadowCost = -1.0;

                        // Compute shadow costs for the actions and find the action with the minimum shadow cost
                        if (state.Actions != null && state.Actions.Count > 0)
                        {
                            foreach (WeibullMarkovAction action in state.Actions)
                            {
                                if (action.IsApplicable)
                                {
                                    Double shCost = action.Cost;
                                    for (Int32 j = 0; j < action.TranProb.Length; j++)
                                    {
                                        Double frac = action.TranProb[j];
                                        shCost += _discFactor * frac * States[j].ShadowCost;
                                    }

                                    action.AssignShadowCost(shCost);

                                    Log.InfoFormat("Iter: {0}\tCondition state: {1}\tAction: {2}\tAgency cost: {3:f2}\tShadow cost: {4:f2}",
                                                   numIter, state.Number, action.Number, action.Cost, action.ShadowCost);

                                    if (minCostActionNumber < 0 || Math.Round(minActionShadowCost, 2) > Math.Round(shCost, 2))
                                    {
                                        minCostActionNumber = action.Number;
                                        minActionShadowCost = shCost;
                                    }
                                }
                            }
                        }


                        Log.InfoFormat("Iter: {0}\tCondition state: {1}\tMinShadowCostAction: {2}\tMinActionShadowCost: {3:f2}\tDo-Nothing direct (agency) cost: {4:f2}",
                                       numIter,
                                       state.Number,
                                       minCostActionNumber,
                                       minActionShadowCost,
                                       state.DoNothingCost);

                        if (i == NS - 1)    // If it is the last condition state
                        {
                            Log.InfoFormat("Iter: {0}\tLast state processing", numIter);

                            if (!this.FailCostEstimate)
                            {
                                Double d = Weibull.ShadowCostAtAgeZero(state.Eta, state.Beta, FailureCost, _discFactor, out state.ShadowCostStreamLength, ref state.ShadowCostStream, out errorMessage);

                                Log.InfoFormat("Iter: {0}\tLast state processing\tDN-ShadowCost-in-Year-One: {1} - computed", numIter, d);

                                if (d < 0.0)
                                {
                                    throw new Exception(errorMessage);
                                }

                                d += state.DoNothingCost;

                                if (d < minActionShadowCost && 0 < minCostActionNumber && FailCostOverride)
                                {
                                    d = minActionShadowCost + 0.01;
                                    failCostOverridden = true;
                                    Log.InfoFormat("Iter: {0}\tLast state processing\tDN-ShadowCost-in-Year-One: {1} - as to override", numIter, d);
                                }
                                else
                                {
                                    Log.InfoFormat("Iter: {0}\tLast state processing\tDN-ShadowCost-in-Year-One: {1} - as computed", numIter, d);
                                }

                                state.AssignDoNothingShadowCostAgeZero(d);
                            }
                            else
                            {
                                Double d = minActionShadowCost + 0.01;
                                state.AssignDoNothingShadowCostAgeZero(d);
                                Log.InfoFormat("Iter: {0}\tLast state processing\tDN-ShadowCost-in-Year-One: {1} - estimated", numIter, d);
                            }
                        }
                        else  // ..., i.e. if it is NOT the last state
                        {
                            Double d = Weibull.ShadowCostAtAgeZero(state.Eta, state.Beta, States[i + 1].ShadowCost, _discFactor, out state.ShadowCostStreamLength, ref state.ShadowCostStream, out errorMessage);
                            d += state.DoNothingCost;
                            state.AssignDoNothingShadowCostAgeZero(d);
                            Log.InfoFormat("Iter: {0}\tCondition state: {1}\tDN-ShadowCost-in-Year-One: {2} - as computed", numIter, state.Number, d);
                        }

                        if (minCostActionNumber > 0)
                        {
                            state.AssignMinShadowCostAction(minCostActionNumber);
                        }

                        // Assign action that has the minimum shadow cost (including do nothing)
                        if (minCostActionNumber < 0 || state.DoNothingShadowCostAgeZero < minActionShadowCost)
                        {
                            state.AssignAction(0);
                        }
                        else
                        {
                            state.AssignAction(minCostActionNumber);
                        }

                        Log.InfoFormat("Iter: {0}\tCondition state: {1}\tAssigned action: {2}\tShadow cost: {3}", numIter, state.Number, state.AssignedAction, state.ShadowCost);

                        noChange = noChange && state.NoChange;

                        Log.InfoFormat("Iter: {0}\tCondition state: {1}\tConvergence achieved: {2}", numIter, state.Number, noChange);
                    }
                } while (!noChange && numIter <= _maxIter);

                Log.InfoFormat("Main iteration cycle ended. Converted: {0}.  Iterations run: {1}", noChange, numIter);

                if (!noChange)
                {
                    String err = String.Format("Conversion of the main iteration cycle has not been achieved after {0} iterations.", numIter);
                    throw new Exception(err);
                }

                if (FailCostEstimate || failCostOverridden)
                {
                    Log.InfoFormat("Calculating the failure cost.");

                    WeibullMarkovConditionState lastS = States[NS - 1];

                    if (lastS.ff == 0.0 || lastS.Beta == 1.0)
                    {
                        // f cannot be zero because it has been checked against it in the beginning
                        Double f = Weibull.OneYearFailure(lastS.Eta, lastS.Beta, 1.0);
                        Double d = lastS.DoNothingShadowCostAgeZero - lastS.DoNothingCost;
                        failureCost = d * (1.0 - _discFactor * (1.0 - f)) / f;
                        // we are done because f does not change over time

                        Log.InfoFormat("For constant deterioration rate failure cost is: {0:f2}", failureCost);
                    }
                    else
                    {
                        if (!FailCostEstimate)
                        {
                            failureCost = FailureCost;  // Use user's input as the first approximation
                        }
                        else //..., i.e. if it has to be calculated from scratch (FailCostEstimate==True)
                        {
                            Double f = Weibull.OneYearFailure(lastS.Eta, lastS.Beta, 1.0);
                            // To calculate the first approximation we use 1% of failure if actual fialure probability is
                            // less than 0.01 at age zero (in year 1)
                            f = Math.Max(f, 0.01);
                            Double d = lastS.DoNothingShadowCostAgeZero - lastS.DoNothingCost;
                            failureCost = d * (1.0 - _discFactor * (1.0 - f)) / f;
                        }

                        Log.InfoFormat("Failure cost approximation: {0:f2}", failureCost);

                        // Make sure that the crude estimate of failure cost delivers tangible (i.e. >=0.01) cost at age zero (year 1).
                        Double ageZeroCost = 0.0;
                        Int32  n           = 0;

                        while (n < _maxIter)
                        {
                            ageZeroCost = Weibull.ShadowCostAtAgeZero(lastS.Eta, lastS.Beta, failureCost, _discFactor, out lastS.ShadowCostStreamLength, ref lastS.ShadowCostStream, out errorMessage);
                            if (ageZeroCost < 0.0)
                            {
                                throw new Exception(errorMessage);
                            }
                            if (Math.Round(ageZeroCost, 3) <= 0.005)  // If we are loosing precision multiply failure cost by 10 and try again
                            {
                                failureCost *= 10.0;
                            }
                            else
                            {
                                break;
                            }
                            n++;

                            Log.InfoFormat("Failure cost approximation iteration {0}. Shadow cost at age zero if approximated failure cost used: {0}", n, ageZeroCost);
                        }

                        if (n >= _maxIter)
                        {
                            String err = String.Format("Failure cost could not be estimated after {0} iterations.", n);
                            throw new Exception(err);
                        }

                        // From this moment on we do not pay attention to the direct do-nothing cost and compare only their
                        // shadow-cost (i.e. failure-cost related components).

                        // Adjsut it making use of linearity
                        Log.InfoFormat("Shadow cost at age-zero - used by the policy: {0:f2}", lastS.DoNothingShadowCostAgeZero);
                        Log.InfoFormat("Failure-related shadow cost at age-zero - used by the policy: {0:f2}", lastS.DoNothingShadowCostAgeZero - lastS.DoNothingCost);

                        failureCost *= (lastS.DoNothingShadowCostAgeZero - lastS.DoNothingCost);
                        failureCost /= ageZeroCost;

                        Log.InfoFormat("Adjusted failure cost: {0:f2}", failureCost);


                        ageZeroCost = Weibull.ShadowCostAtAgeZero(lastS.Eta, lastS.Beta, failureCost, _discFactor, out lastS.ShadowCostStreamLength, ref lastS.ShadowCostStream, out errorMessage);

                        Log.InfoFormat("Failure-related shadow cost at age zero - adjusted: {0:f2}", ageZeroCost);

                        if (ageZeroCost < 0.0)
                        {
                            throw new Exception(errorMessage);
                        }

                        if (Math.Round(ageZeroCost, 2) != Math.Round(lastS.DoNothingShadowCostAgeZero, 2))
                        {
                            Log.WarnFormat("Computed failure cost ({0:f2}) does not deliver the required shadow cost in year one ({1:f2}). The value it delivers is {2:f2}",
                                           failureCost, lastS.DoNothingShadowCostAgeZero, ageZeroCost);
                        }
                    }
                }

                Log.InfoFormat("Generating policy - started.");
                FailureCostUsedInPolicy = failureCost;
                Log.InfoFormat("Failure cost used: {0}", FailureCostUsedInPolicy);
                Log.InfoFormat("Alpha-adjusted failure cost used: {0}", FailureCostUsedInPolicy * _discFactor);

                for (Int32 j = 0; j < NS; j++)
                {
                    WeibullMarkovConditionState state = States[j];
                    state.Recommendations.Clear();

                    Double dTarget = (j == NS - 1) ? FailureCostUsedInPolicy.Value : States[j + 1].DoNothingShadowCostAgeZero;
                    dTarget *= _discFactor;

                    Log.InfoFormat("Condition state: {0}\tTargetCost: {1}", state.Number, state.ff >= 0.0 ? dTarget : 0.0);

                    Int32 yMax = Math.Min(numYears, state.ShadowCostStreamLength);

                    // The first pass is to establish yMax, the second one is to add recommendations

                    for (Int32 iPass = 0; iPass < 2; iPass++)
                    {
                        for (Int32 y = 0; y < yMax; y++)
                        {
                            Double d = state.ShadowCostStream[y];
                            Int32  A = 0;
                            Double B = 0.0;
                            if (0 < state.MinShadowCostAction && Math.Round(d + state.DoNothingCost - state.MinActionShadowCost, 2) >= 0.01)
                            {
                                A = state.MinShadowCostAction;
                                B = Math.Round(d - state.MinActionShadowCost, 2);
                            }

                            if (iPass > 0)
                            {
                                WeibullMarkovStatePolicyRecommendation R = new WeibullMarkovStatePolicyRecommendation(y + 1, A, B);
                                state.Recommendations.Add(R);
                            }

                            Log.InfoFormat("Pass: {7}\tState: {0}\tYear={1,3:d}\tShC={2,9:f2}\tMinAct={3,3}\tMinActShC={4,9:f2}\tActRec={5}\tUnitBenf={6,9:f2}",
                                           state.Number, y + 1, d, state.MinShadowCostAction, state.MinActionShadowCost, A, B, iPass);

                            if (state.ff == 0.0 || state.Beta == 1.0)
                            {
                                yMax = 1;
                                break;
                            }

                            if (iPass == 0 && A == 0 && state.ff >= 0.0 && y == yMax - 1)
                            {
                                // If f-probability is growing but it is still do nothing in year yMax then cut it at the first year;
                                yMax = 1;
                                break;
                            }

                            // Saturation conditions
                            if (iPass == 0 && y > 0 && state.ff >= 0.0 && Math.Round(d, 2) >= Math.Round(dTarget, 2))
                            {
                                yMax = A == 0 ? 1 : y + 1;
                                break;
                            }

                            if (iPass == 0 && y > 0 && state.ff < 0.0 && Math.Round(d, 2) <= 0.0)
                            {
                                yMax = y + 1;
                                break;
                            }

                            if (iPass == 0 && A == 0 && state.ff <= 0.0)
                            {
                                yMax = y + 1;
                                break;  // If f-probability is decreasing and it is do-nothing in year Y then it can only be  do-nothing later
                            }
                        }
                    }
                }

                Log.InfoFormat("Generating policy - ended.");
            }
            catch (Exception ex)
            {
                errorMessage = ex.Message;
                ok           = false;
                Log.Error(errorMessage);
            }

            Log.InfoFormat("Solving Weibull-Markov Decision model - ended. OK={0}", ok);

            return(ok);
        }