All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.powertac.common.TariffEvaluator Maven / Gradle / Ivy

/*
 * Copyright (c) 2013 by the original author
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.powertac.common;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.powertac.common.interfaces.CustomerModelAccessor;
import org.powertac.common.interfaces.TariffMarket;
import org.powertac.common.repo.TariffRepo;
import org.powertac.common.repo.TariffSubscriptionRepo;
import org.powertac.common.spring.SpringApplicationContext;

/**
 * Tariff evaluation process intended to simplify customer models.
 * There should be one of these created for each CustomerInfo
 * instance within a customer model, since
 * tariff cost values are cached, and are dependent on PowerType.
 * 
 * @author John Collins
 */
public class TariffEvaluator
{
  static private Logger log = LogManager.getLogger(TariffEvaluator.class.getName());

  // component dependencies
  TariffRepo tariffRepo;
  TariffMarket tariffMarket;
  TariffSubscriptionRepo tariffSubscriptionRepo;

  // access to customer model
  private CustomerModelAccessor accessor;
  private CustomerInfo customerInfo;

  // inconvenience factors
  private double touFactorCap = 0.0;// disabling touFactor since it's incorrect
  private double touFactor = Math.min(0.2, touFactorCap); // 0.2;
  private double tieredRateFactor = 0.1;
  private double variablePricingFactor = 0.5;
  private double interruptibilityFactor = 0.2;

  // amortization period for negative signup payments
  private long signupFeePeriod = 6 * TimeService.HOUR;

  // minimum usable tariff-expiration interval
  private long minExpirationInterval = TimeService.DAY;

  // profile cost analyzer
  private TariffEvaluationHelper helper;

  // per-customer parameter settings
  private int chunkSize = 1; // max size of allocation chunks
  private int maxChunkCount = 200; // max number of chunks
  private int tariffEvalDepth = 5; // # of tariffs/powerType to eval
  private double inertia = 0.8;
  private double signupBonusFactor = 0.1; // inertia multiplier for signup bonus
  private double rationality = 0.9;
  private double inconvenienceWeight = 0.2;
  private double tariffSwitchFactor = 0.04;
  private double preferredDuration = 6;
  private boolean evaluateAllTariffs = false;

  // state
  private int evaluationCounter = 0;
  private HashMap evaluatedTariffs;
  private HashMap allocations;

  // algorithm parameters - needed for numerical stablity
  private double lambdaMax = 50.0;
  private double maxLinearUtility = 7.0;
  private int stdDuration = 2; // two-day standardized profile length
  private int profileLength = 168; // length of customer-supplied profile

  public TariffEvaluator (CustomerModelAccessor cma)
  {
    accessor = cma;
    customerInfo = cma.getCustomerInfo();
    helper = new TariffEvaluationHelper();
    evaluatedTariffs = new HashMap<>();
    allocations = new LinkedHashMap<>();
  }

  // convenience method for logging support
  private String getName()
  {
    return customerInfo.getName();
  }

  /**
   * Delegates profile cost factors to helper.
   */
  public void initializeCostFactors (double wtExpected, double wtMax,
                                     double wtRealized, double soldThreshold)
  {
    helper.initializeCostFactors(wtExpected, wtMax, wtRealized, soldThreshold);
  }

  /**
   * Initializes the per-tariff inconvenience factors.
   * These are not normalized; it is up to the customer model to normalize the
   * per-tariff and cross-tariff factors as appropriate
   */
  public void initializeInconvenienceFactors (double touFactor,
                                              double tieredRateFactor,
                                              double variablePricingFactor,
                                              double interruptibilityFactor)
  {
    this.touFactor = Math.min(touFactor, touFactorCap); //touFactor;
    this.tieredRateFactor = tieredRateFactor;
    this.variablePricingFactor = variablePricingFactor;
    this.interruptibilityFactor = interruptibilityFactor;
  }

  /**
   * Initializes the per-timeslot regulation-capacity estimates.
   * All three represent per-timeslot estimates of exercised regulation
   * capacity, and are applicable only for tariffs with regulation rates.
   * Note that the expectedDischarge parameter only applies to
   * storage devices that can be discharged (batteries, pumped storage).
   * Values are from the customer's viewpoint, so curtailment and discharge
   * are negative (less energy for the customer) and down-regulation
   * is positive.
   * Default value for each of these factors is zero.
   */
  public void initializeRegulationFactors (double expectedCurtailment,
                                           double expectedDischarge,
                                           double expectedDownRegulation)
  {
    double expCurtail = expectedCurtailment;
    if (expCurtail > 0.0) {
      log.error(getName() + ": expectedCurtailment " + expCurtail
                + " must be non-positive");
      expCurtail = 0.0;
    }
    double expDis = expectedDischarge;
    if (expDis > 0.0) {
      log.error(getName() + ": expectedDischarge " + expDis
                + " must be non-positive");
      expDis = 0.0;
    }
    double expDown = expectedDownRegulation;
    if (expDown < 0.0) {
      log.error(getName() + ": expectedDownRegulation " + expDown
                + " must be non-negative");
      expDown = 0.0;
    }
    helper.initializeRegulationFactors(expCurtail, expDis, expDown);
  }

  // parameter settings
  /**
   * Sets the target size of allocation chunks. Default is 1. Actual
   * chunk size will be at least 0.5% of the population size.
   */
  public TariffEvaluator withChunkSize (int size)
  {
    if (size > 0)
      chunkSize = size;
    else
      log.error("chunk size " + size + " < 0");
    return this;
  }

  /**
   * Sets the number of tariffs/broker of each applicable PowerType
   * to consider. Default is 5, which means that only the 5 most recent
   * tariffs of each applicable PowerType from each broker are considered.
   * So if there are 10 brokers, and the PowerType is ELECTRIC_VEHICLE,
   * the applicable PowerTypes would be CONSUMPTION, INTERRUPTIBLE_CONSUMPTION,
   * and ELECTRIC_VEHICLE. If each broker has published at least five tariffs
   * for each of these types, the default value of 5 would result in evaluating
   * up to 150 alternative tariffs in addition to the currently subscribed
   * tariff(s).
   */
  public TariffEvaluator withTariffEvalDepth (int depth)
  {
    tariffEvalDepth = depth;
    return this;
  }

  /**
   * If true, then tariff evaluations are not saved; instead, all tariffs
   * are evaluated each time. This is needed for customers that generate
   * usage profiles that are sensitive to current conditions or state.
   */
  public TariffEvaluator withEvaluateAllTariffs (boolean value)
  {
    evaluateAllTariffs = value;
    return this;
  }

  /**
   * Sets the steady-state evaluation inertia for the customer. This is a
   * value in [0,1], where 0 is no inertia (always evaluates), and 1 is
   * total couch-potato inertia (never bothers to evaluate). The instantaneous
   * value starts at zero, so customers will have a chance to switch away
   * from the default tariff at the beginning of a sim, and is temporarily
   * set to zero when a tariff is revoked. Default value is 0.8.
   */
  public TariffEvaluator withInertia (double inertia)
  {
    this.inertia = inertia;
    return this;
  }

  /**
   * Sets the factor by which inertia is multiplied for the case where
   * the current tariff had a positive signup bonus. Should be 0 <= sbf <= 1.
   * Default value is zero.
   */
  public TariffEvaluator withSignupBonusFactor (double factor)
  {
    this.signupBonusFactor = factor;
    return this;
  }

  /**
   * Sets the level of rationality for this customer.
   * Household customers are expected to have lower rationality than
   * business/industrial/institutional customers.
   * 1.0 is fully rational, 0.5 is quite irrational. A value of zero will
   * result in random choices. Default value is 0.9.
   */
  public TariffEvaluator withRationality (double rationality)
  {
    this.rationality = rationality;
    if (rationality < 0.0) {
      log.error("Rationality " + rationality + "< 0.0");
      this.rationality = 0.01;
    }
    else if (rationality > 1.0) {
      log.error("Rationality " + rationality + "> 1.0");
      this.rationality = 1.0;
    }
    return this;
  }

  /**
   * Sets the weight given to inconvenience (as opposed to cost)
   * in computing tariff utility. Must be
   * in the range [0,1]. Default value is 0.2.
   */
  public TariffEvaluator withInconvenienceWeight (double weight)
  {
    this.inconvenienceWeight = weight;
    return this;
  }

  /**
   * Sets the inconvenience of switching tariffs. Default value is 0.04.
   */
  public TariffEvaluator withTariffSwitchFactor(double factor)
  {
    this.tariffSwitchFactor = factor;
    return this;
  }

  /**
   * Sets the preferred maximum contract duration in days. For tariffs
   * having a non-zero early-withdrawal fee, this is the period after which
   * the cost of withdrawal is discounted. It is also the standard period
   * over which usage cost is compared against signup/withdrawal payments.
   * Default value is 6 days.
   */
  public TariffEvaluator withPreferredContractDuration (double days)
  {
    this.preferredDuration  = days;
    return this;
  }

  /**
   * Sets the length of the customer-supplied profile. Used internally and
   * for test support.
   */
  void setProfileLength (int length)
  {
    profileLength = length;
  }

  int getProfileLength ()
  {
    return profileLength;
  }

  /**
   * Returns the eval scale factor, the ratio of the stdDuration to the
   * preferredDuration.
   */
  double getScaleFactor ()
  {
    return (double)stdDuration * 24.0 / (double)getProfileLength();
  }

  /**
   * Evaluates tariffs and updates subscriptions
   * for a single customer model with a single power type.
   * Also handles tariff revoke/supersede. This requires that each
   * Customer model call this method once on each tariff publication cycle.
   */
  public void evaluateTariffs ()
  {
    allocations.clear();
    HashSet newTariffs = new LinkedHashSet<>(getTariffRepo()
              .findRecentActiveTariffs(tariffEvalDepth,
                                       customerInfo.getPowerType()));

    // make sure all superseding tariffs are in the set
    addSupersedingTariffs(newTariffs);

    // adjust inertia for BOG, accounting for the extra
    // evaluation cycle at ts 0
    double actualInertia =
            Math.max(0.0,
                     (1.0 - Math.pow(2, 1 - evaluationCounter)) * inertia);
    evaluationCounter += 1;

    // Get the cost eval for the appropriate default tariff
    EvalData defaultEval = getDefaultTariffEval();
    log.debug("customer {}: defaultEval={}",
              getName(), defaultEval.costEstimate);
    
    // ensure we have the cost eval for each of the new tariffs
    for (Tariff tariff : newTariffs) {
      EvalData eval = evaluatedTariffs.get(tariff);
      if (evaluateAllTariffs || null == eval) {
        // compute the projected cost for this tariff
        double cost = forecastCost(tariff);
        double hassle = computeInconvenience(tariff);
        log.info("Evaluated tariff " + tariff.getId()
                 + ": cost=" + cost
                 + ", inconvenience=" + hassle);
        eval = new EvalData(cost, hassle);
        evaluatedTariffs.put(tariff, eval);
      }
    }

    // Iterate through the current active subscriptions
    for (TariffSubscription subscription
            : getTariffSubscriptionRepo().
            findActiveSubscriptionsForCustomer(customerInfo)) {
      Tariff subTariff = subscription.getTariff();
      // find out how many of these customers can withdraw without penalty
      double withdrawCost = subTariff.getEarlyWithdrawPayment();
      int committedCount = subscription.getCustomersCommitted();
      int expiredCount = subscription.getExpiredCustomerCount();
      if (withdrawCost == 0.0 || expiredCount == committedCount) {
        // no need to worry about expiration
        evaluateAlternativeTariffs(subscription, actualInertia,
                                   0.0, committedCount,
                                   getDefaultTariff(),
                                   defaultEval, newTariffs);
      }
      else {
        // Evaluate expired and unexpired subsets separately
        evaluateAlternativeTariffs(subscription, actualInertia,
                                   0.0, expiredCount,
                                   getDefaultTariff(),
                                   defaultEval, newTariffs);
        evaluateAlternativeTariffs(subscription, actualInertia,
                                   withdrawCost, committedCount - expiredCount,
                                   getDefaultTariff(), 
                                   defaultEval, newTariffs);
      }
    }
    updateSubscriptions();
  }

  // Ensures that superseding tariffs are evaluated by adding them
  // to the newTariffs list
  private void addSupersedingTariffs (HashSet newTariffs)
  {
    List revokedSubscriptions =
            getTariffSubscriptionRepo().getRevokedSubscriptionList(customerInfo);
    for (TariffSubscription sub : revokedSubscriptions) {
      Tariff supTariff = sub.getTariff().getIsSupersededBy();
      if (null != supTariff && supTariff.isSubscribable())
        newTariffs.add(supTariff);
    }
  }

  // evaluate alternatives
  private void evaluateAlternativeTariffs (TariffSubscription current,
                                           double inertia,
                                           double withdraw0,
                                           int population,
                                           Tariff defaultTariff,
                                           EvalData defaultEval,
                                           Set initialTariffs)
  {
    //log.info("evaluateAlternativeTariffs(" + current.getTariff().getId() + ")");
    // Associate each alternate tariff with its utility value
    TreeSet evals = new TreeSet<>();
    HashSet tariffs = new HashSet<>(initialTariffs);
    tariffs.add(defaultTariff);

    // Check whether the current tariff is revoked, add it if not
    Tariff currentTariff = current.getTariff();
    boolean revoked = false;
    Tariff replacementTariff = null;
    if (currentTariff.getState() == Tariff.State.KILLED) {
      revoked = true;
      replacementTariff = currentTariff.getIsSupersededBy();
      log.info("Customer " + customerInfo.getName() + ": tariff "
               + currentTariff.getId() + " revoked, superseded by "
               + ((null == replacementTariff)
                       ? "default": replacementTariff.getId()));
      if (null == replacementTariff) {
        replacementTariff = defaultTariff;
      }
      withdraw0 = 0.0; // withdraw without penalty
    }
    else {
      tariffs.add(currentTariff);
    }

    // Compute the final cost number for each tariff
    HashMap costs = new HashMap();
    double maxCost = 0.0;
    double signupCost = 0.0;
    for (Tariff tariff: tariffs) {
      EvalData eval = evaluatedTariffs.get(tariff);
      double inconvenience = eval.inconvenience;
      double cost = eval.costEstimate;
      if (tariff != currentTariff
              && tariff != replacementTariff) {
        // add in tariff-switch factors
        inconvenience += tariffSwitchFactor;
        if (tariff.getBroker() != currentTariff.getBroker()) {
          inconvenience +=
                  accessor.getBrokerSwitchFactor(revoked);
        }
        signupCost = computeSignupCost(tariff);
        cost += signupCost;
        cost += withdraw0; // withdraw from current tariff
        cost += computeWithdrawCost(tariff);
        if (Double.isNaN(cost)) {
          log.error(getName() + ": cost is NaN for tariff "
                    + tariff.getId());
        }
      }
      costs.put(tariff, new EvalData(cost, inconvenience));
    }

    // for each tariff, including the current and default tariffs,
    // compute the utility
    for (Tariff tariff: tariffs) {
      EvalData finalCost = costs.get(tariff);
      // don't consider current tariff if it's revoked
      if (!revoked || tariff != currentTariff) {
        double utility =
            computeNormalizedDifference(finalCost.costEstimate - maxCost,
                                        defaultEval.costEstimate - maxCost);
        utility -= inconvenienceWeight * finalCost.inconvenience;
        //log.info("adding TariffUtility(" + tariff.getId() + ", " + constrainUtility(utility) + " (" + utility + ")");
        if (Double.isNaN(utility)) {
          log.error(getName() + ": utility is NaN for tariff "
                    + tariff.getId());
        }
        log.debug("tariff {}: maxCost={}, adjCost={}, default={}, utility={}",
                  tariff.getId(), maxCost, finalCost.costEstimate - maxCost,
                  defaultEval.costEstimate - maxCost, utility);
        TariffUtility tu =
            new TariffUtility(tariff, constrainUtility(utility));
        evals.add(tu);
      }
    }
    
    // We now have utility values for each possible tariff.
    // Time to make some choices -
    // -- first, compute lambda from rationality
    // -- second, we have to compute the sum of transformed utilities
    double logitDenominator = 0.0;
    double lambda = Math.pow(lambdaMax, rationality) - 1.0;
    for (TariffUtility util : evals) {
      logitDenominator +=
              Math.exp(lambda * util.utility);
    }
    // then we can compute the probabilities
    for (TariffUtility util : evals) {
      util.probability =
              Math.exp(lambda * util.utility)
              / logitDenominator;
      if (Double.isNaN(util.probability)) {
        log.error(getName() + ": Probability NAN, util=" + util.utility
                  + ", denom=" + logitDenominator
                  + ", tariff " + util.tariff);
        util.probability = 0.0;
      }
      log.debug("Tariff {} probability={}",
                util.tariff.getId(), util.probability);
    }
    int remainingPopulation = population;
    int chunk = remainingPopulation;
    if (customerInfo.isMultiContracting()) {
      // Ideally, each individual customer makes a choice.
      // For large populations, we do it in chunks.
      chunk = getChunkSize(population);
    }
    while (remainingPopulation > 0) {
      int count = (int)Math.min(remainingPopulation, chunk);
      remainingPopulation -= count;
      // allocate a chunk
      double inertiaSample = accessor.getInertiaSample();
      if (!revoked && withdraw0 <= 0.0 &&
          signupCost <= 0.0 && inertiaSample < inertia) {
        // skip this one if not processing revoked tariff,
        // or if there is no payment possible from withdrawing,
        // or if the customer was not induced by a positive signup cost,
        // or if the customer is not paying attention.
        continue;
      }
      else if (signupCost > 0.0 &&
          inertiaSample < inertia * signupBonusFactor) {
        // Use lower inertia in case the current tariff had a signup bonus
        continue;
      }
      double tariffSample = accessor.getTariffChoiceSample();
      // walk down the list until we run out of probability
      boolean allocated = false;
      for (TariffUtility tu : evals) {
        log.debug("tariff {}: sample={}, probability={}",
                  tu.tariff.getId(), tariffSample, tu.probability);
        if (tariffSample <= tu.probability) {
          addAllocation(currentTariff, tu.tariff, count);
          allocated = true;
          break;
        }
        else {
          tariffSample -= tu.probability;
        }
      }
      if (!allocated) {
        log.error(getName() + ": Failed to allocate: P=" + tariffSample);
      }
    }
  }

  // Customers really, really don't like paying to sign up. This computation
  // inflates the cost of signup fees by the ratio of the customer's
  // preferred duration to the duration of one tariff-publication cycle.
  // On the other hand, positive signup payments are scaled to amortize over
  // just the standard eval duration.
  double computeSignupCost (Tariff tariff)
  {
    if (tariff.getSignupPayment() < 0.0) {
      // penalize negative signup fees
      return tariff.getSignupPayment() *
          preferredDuration * TimeService.DAY / signupFeePeriod;
    }
    else {
      return tariff.getSignupPayment() * getScaleFactor();
    }
  }

  // If the tariff has a non-zero minDuration and a negative
  // earlyWithdrawPayment, then we prefer shorter values for minDuration.
  double computeWithdrawCost (Tariff tariff)
  {
    if (tariff.getMinDuration() <= minExpirationInterval
        || 0.0 == tariff.getEarlyWithdrawPayment()) {
      return 0.0;
    }
    double annoyance = 1.0;
    if (tariff.getEarlyWithdrawPayment() < 0.0) {
      annoyance =
          (double)tariff.getMinDuration()
          / (double)(preferredDuration * TimeService.DAY);
    }
    double scale = annoyance * getScaleFactor();
    return tariff.getEarlyWithdrawPayment() * scale;
  }

  // Ensures numeric stability by constraining range of utility values.
  private double constrainUtility (double utility)
  {
    if (utility > maxLinearUtility) {
      double compressed = Math.log10(utility - maxLinearUtility);
      return Math.min(maxLinearUtility + compressed,
                      maxLinearUtility * 2);
    }
    else if (utility < -maxLinearUtility) {
      return -maxLinearUtility; // these will never be chosen anyway
    }
    else
      return utility;
  }

  // Computes the normalized difference between the cost of the default tariff
  // and the cost of a proposed tariff
  private double computeNormalizedDifference (double cost, double defaultCost)
  {
    // Daniel temporary bug fix
    // John -- TODO -- I REALLY don't like this
    if (defaultCost == 0) {
      // this means that capacity is 0, so we don't want any changes
      // so return small utility
      return 0;
    }
    double ndiff = (defaultCost - cost) / defaultCost;
    if (customerInfo.getPowerType().isProduction())
      ndiff = -ndiff;
    return ndiff;
  }

  // Retrieves default tariff
  private Tariff getDefaultTariff ()
  {
    return getTariffMarket().getDefaultTariff(customerInfo.getPowerType());
  }

  private EvalData getDefaultTariffEval ()
  {
    Tariff defaultTariff = getDefaultTariff();
    EvalData defaultEval = evaluatedTariffs.get(defaultTariff);
    if (null == defaultEval) {
      defaultEval = new EvalData(forecastCost(defaultTariff), 0.0);
      evaluatedTariffs.put(defaultTariff, defaultEval);
    }
    return defaultEval;
  }
  
  // Cost forecaster
  private double forecastCost (Tariff tariff)
  {
    CapacityProfile profile = accessor.getCapacityProfile(tariff);
    if (0 == profile.getProfile().length) {
      log.error("Zero-length profile for " + customerInfo.getName());
      return 0.0;
    }
    setProfileLength(profile.getProfile().length);
    // NOTE: must call the next function after the previous,
    // since the previous writes inconv. factors.
    // Always 0 except for AdaptiveCapacityOriginator
    double inconv = accessor.getShiftingInconvenienceFactor(tariff);
    double profileCost = helper.estimateCost(tariff,
                                             profile.getProfile(),
                                             profile.getStart());
    if (Double.isNaN(profileCost)) {
      log.error(getName() + ": profile cost NaN for tariff "
                + tariff.getId());
    }
    //double scale = preferredDuration * 24.0 / profile.length;
    double scale = stdDuration * 24.0 / getProfileLength();
    if (Double.isNaN(scale)) {
      log.error(getName() + ": scale NaN for tariff " + tariff.getId());
    }
    log.debug("tariff {}: profileCost={}, inconv={}, scaled-charge={}, scaled (cost+inconv)={}",
              tariff.getId(), profileCost, inconv, profileCost * scale, (profileCost + inconv) * scale,
              (profileCost + inconv) * scale / (profileCost * scale));
    return (profileCost + inconv) * scale;
  }

  // tracks additions and deletions for tariff subscriptions
  private void addAllocation (Tariff current, Tariff newTariff, int count)
  {
    if (current == newTariff)
      // ignore no-change allocations
      return;
    // decrement the old
    Integer ac = allocations.get(current);
    if (null == ac)
      ac = -count;
    else
      ac -= count;
    allocations.put(current, ac);
    // increment the new
    ac = allocations.get(newTariff);
    if (null == ac)
      // first time on this one
      ac = count;
    else
      ac += count;
    allocations.put(newTariff, ac);
  }
  
  // updates subscriptions based on computed allocations
  private void updateSubscriptions ()
  {
    int check = 0;
    for (Tariff tariff : allocations.keySet()) {
      int count = allocations.get(tariff);
      check += count;
      if (count < 0) {
        //unsubscribe
        TariffSubscription sub =
                getTariffSubscriptionRepo().findSubscriptionForTariffAndCustomer
                  (tariff, customerInfo);
        sub.unsubscribe(-count);
        log.info("customer " + customerInfo.getName()
                 + " unsubscribes " + -count
                 + " from tariff " + tariff.getId());
      }
      else if (count > 0) {
        // subscribe
        getTariffMarket().subscribeToTariff(tariff, customerInfo, count);
        log.info("customer " + customerInfo.getName()
                 + " subscribes " + count
                 + " to tariff " + tariff.getId());
      }
    }
    // sanity check
    if (check != 0) {
      log.error("Subscription updates do not add up for "
                + customerInfo.getName() + ": " + check);
    }
  }

  // inconvenience computation
  /**
   * Returns inconvenience of time-of-use rate.
   */
  public double getTouFactor ()
  {
    return touFactor;
  }

  /**
   * Returns inconvenience of tiered rate.
   */
  public double getTieredRateFactor ()
  {
    return tieredRateFactor;
  }

  /**
   * Returns inconvenience of variable pricing.
   */
  public double getVariablePricingFactor ()
  {
    return variablePricingFactor;
  }

  /**
   * Returns inconvenience of interruptibility.
   */
  public double getInterruptibilityFactor ()
  {
    return interruptibilityFactor;
  }

  /**
   * Computes composite per-tariff inconvenience of a tariff.
   */
  public double computeInconvenience (Tariff tariff)
  {
    double result = 0.0;
    // Time-of-use tariffs have multiple Rates, at least one of which
    // has a daily or weekly begin/end
    if (tariff.isTimeOfUse())
      result += touFactor;

    // Tiered tariffs have multiple Rates, at least one having
    // a non-zero tier threshold.
    if (tariff.isTiered())
      result += tieredRateFactor;

    // Variable-rate tariffs have at least one non-fixed Rate
    if (tariff.isVariableRate())
      result += variablePricingFactor;

    // Interruptible tariffs are for an interruptible PowerType, and
    // have a Rate with a maxCurtailment != 0
    if (tariff.isInterruptible())
      result += interruptibilityFactor;
    return result;
  }
  
  // returns the correct chunk size for a given population
  private int getChunkSize (int population)
  {
    if (population <= chunkSize)
      return population;
    else
      return Math.max(population / maxChunkCount, chunkSize);
  }

  // Spring component access ------------------------------------------
  private TariffRepo getTariffRepo ()
  {
    if (null != tariffRepo)
      return tariffRepo;
    tariffRepo =
            (TariffRepo) SpringApplicationContext.getBean("tariffRepo");
    return tariffRepo;
  }

  private TariffSubscriptionRepo getTariffSubscriptionRepo ()
  {
    if (null != tariffSubscriptionRepo)
      return tariffSubscriptionRepo;
    tariffSubscriptionRepo =
            (TariffSubscriptionRepo) SpringApplicationContext.getBean("tariffSubscriptionRepo");
    return tariffSubscriptionRepo;
  }

  private TariffMarket getTariffMarket ()
  {
    if (null != tariffMarket)
      return tariffMarket;
    tariffMarket =
            (TariffMarket) SpringApplicationContext.getBean("tariffMarketService");
    return tariffMarket;
  }

  // Container for tariff-utility recording ------------------------------
  class TariffUtility implements Comparable
  {
    Tariff tariff;
    double adjustedCost = 0.0;
    double utility;
    double probability = 0.0;

    TariffUtility (Tariff tariff, double utility)
    {
      super();
      this.tariff = tariff;
      this.utility = utility;
    }

    @Override
    // natural ordering is by decreasing utility values
      public
      int compareTo (TariffUtility other)
    {
      double result = other.utility - utility;
      if (result == 0.0)
        // consistent with equals...
        return (int) (other.tariff.getId() - tariff.getId());
      else if (result > 0.0)
        return 1;
      else
        return -1;
    }
  }

  // Container for tariff-evaluation data
  class EvalData
  {
    double costEstimate;
    double inconvenience;

    EvalData (double cost, double inconvenience)
    {
      super();
      this.costEstimate = cost;
      this.inconvenience = inconvenience;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy