/* Domestic Appliance Model - Simulation Example Code
Copyright (C) 2008 Ian Richardson, Murray Thomson
CREST (Centre for Renewable Energy Systems Technology),
Department of Electronic and Electrical Engineering
Loughborough University, Leicestershire LE11 3TU, UK
Tel. +44 1509 635326. Email address: I.W.Richardson@lboro.ac.uk
Java implementation (c) 2014 James Keirstead
Imperial College London
j.keirstead@imperial.ac.uk
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 3 of the License, 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 <http://www.gnu.org/licenses/>.
*/
package uk.ac.imperial.simelec;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import cern.jet.random.Uniform;
import cern.jet.random.engine.MersenneTwister;
import cern.jet.random.engine.RandomEngine;
import au.com.bytecode.opencsv.CSVReader;
/**
* Simulates the electricity demand for appliances in a household at one-minute
* intervals for the course of a single day.
*
* @author James Keirstead
*
*/
public class ApplianceModel extends LoadModel<Appliance> {
// Data files
private static String activity_file = "/data/activities.csv";
private static String appliance_file = "/data/appliances.csv";
// Define the relative monthly temperatures
// Data derived from MetOffice temperature data for the Midlands in 2007
// (http://www.metoffice.gov.uk/climate/uk/2007/) Crown Copyright
static double[] oMonthlyRelativeTemperatureModifier = { 1.63, 1.821, 1.595,
0.867, 0.763, 0.191, 0.156, 0.087, 0.399, 0.936, 1.561, 1.994 };
/**
*
* Simulate the electricity demand from appliances for a household at
* one-minute intervals for a single day.
*
* @param args
* takes four arguments, plus one option. The first is an int
* giving the number of residents in the home, the second an int
* giving the month to simulate (1-12), the third is a two-letter
* code indicating weekend (<code>we</code>) or weekday (
* <code>wd</code>), and the fourth is a String giving the output
* directory. The optional fifth argument is an int giving a
* random number seed. If these are not specified, the default is
* to simulate two occupants for a weekday with results saved in
* the current directory.
* @throws IOException
*/
public static void main(String[] args) throws IOException {
int month = 1;
int residents = 2;
boolean weekend = false;
String dir = ".";
OccupancyModel occ = new OccupancyModel(residents, weekend, dir);
// Check arguments
if (args.length == 3 || args.length == 4) {
residents = Integer.valueOf(args[0]);
month = Integer.valueOf(args[1]);
weekend = args[2].equals("we") ? true : false;
dir = args[3];
occ = new OccupancyModel(residents, weekend, dir);
if (args.length == 4)
ApplianceModel.setSeed(Integer.valueOf(args[3]));
} else {
System.out.printf(
"%d arguments detected. Using default arguments.%n",
args.length);
}
ApplianceModel model = new ApplianceModel(month, weekend, dir, occ);
model.run();
}
/**
*
* Create an ApplianceModel with a specified month, weekday, and output
* directory.
*
* @param month
* an integer giving the month of the year to simulate (1-12)
* @param weekend
* a boolean indicating whether to simulate a weekend
* <code>true</code> or weekday <code>false</code>
* @param output_file
* a string giving the path for the output file
* @param model
* an OccupancyModel for calculating when people are present in
* the home
*
*/
public ApplianceModel(int month, boolean weekend, String dir,
OccupancyModel model) {
super(month, weekend, dir, new File(dir, "appliance_output.csv"), model);
}
/**
* Gets a ProbabilityModifier with a specified characteristics from a list.
*
* @param activities
* a List of all ProbabilityModifier objects
* @param weekend
* a boolean indicating whether to to find weekend
* <code>true</code> or weekday <code>false</code> modifiers.
* @param occupants
* an int giving the number of active occupants that should be in
* the ProbabilityModifier.
* @param id
* a String giving an identifier.
* @return a ProbabilityModifier matching all of the specified arguments. If
* none found, then returns <code>null</code>
*/
ProbabilityModifier getProbabilityModifier(
List<ProbabilityModifier> activities, boolean weekend,
int occupants, String id) {
// TODO for six person households, is the best thing just to pretend
// that there are five people there?
occupants = SimElec.validateResidents(occupants);
for (ProbabilityModifier pm : activities) {
if (pm.isWeekend == weekend
&& pm.active_occupant_count == occupants
&& pm.ID.equals(id)) {
return (pm);
}
}
return null;
}
/**
* Randomly assign ownership for appliances.
*
* @param apps
* a List of Appliance objects.
*/
void configure_appliances(List<Appliance> apps) {
for (Appliance a : apps)
a.assignOwnership();
}
/**
* Loads the activity statistics from a file.
*
* @return a List of ProbabilityModifier objects describing each activity.
*
* @throws IOException
*/
List<ProbabilityModifier> loadActivityStatistics() throws IOException {
InputStream is = this.getClass().getResourceAsStream(activity_file);
CSVReader reader = new CSVReader(new InputStreamReader(is), ',', '\'',
6);
List<String[]> activities = reader.readAll();
reader.close();
List<ProbabilityModifier> result = new ArrayList<ProbabilityModifier>();
for (String[] s : activities) {
int bool = Integer.valueOf(s[0]);
boolean weekend = (bool == 1);
int occupants = Integer.valueOf(s[1]);
String ID = s[2].toUpperCase();
ProbabilityModifier stats = new ProbabilityModifier(weekend,
occupants, ID);
for (int i = 0; i < 144; i++) {
stats.modifiers[i] = Double.valueOf(s[i + 3]);
}
result.add(stats);
}
return (result);
}
/**
* Load the appliances from a file.
*
* @return a List of Appliance objects
*
* @throws IOException
*/
List<Appliance> loadAppliances() throws IOException {
InputStream is = this.getClass().getResourceAsStream(appliance_file);
CSVReader reader = new CSVReader(new InputStreamReader(is), ',', '\'',
37);
List<String[]> appliances = reader.readAll();
reader.close();
List<Appliance> results = new ArrayList<Appliance>();
for (String[] s : appliances) {
// TODO energy (column 4, s[3]) not used?
Appliance a = new Appliance(s[0], s[1], Double.valueOf(s[2]),
Integer.valueOf(s[4]), Integer.valueOf(s[5]),
Double.valueOf(s[6]), Integer.valueOf(s[7]),
Integer.valueOf(s[8]), Double.valueOf(s[9]));
results.add(a);
}
return (results);
}
/**
* Describes a factor used to modify the probability of an appliance
* running. Each modifier contains a vector of doubles which describes the
* proportion of households where at least one occupant is engaged in a
* particular activity during a particular ten minute period
*
* @author jkeirste
*
*/
class ProbabilityModifier {
// Class variables
boolean isWeekend;
int active_occupant_count;
String ID;
double[] modifiers = new double[144];
/**
* Create a ProbabilityModified with specified parameters.
*
* @param weekend
* does this apply to a weekend? <code>true</code>
* @param occupants
* an int giving the number of active occupants
* @param id
* a String giving an identifier
*/
private ProbabilityModifier(boolean weekend, int occupants, String id) {
this.isWeekend = weekend;
this.active_occupant_count = occupants;
this.ID = id;
}
}
/**
* Sets the seed for the random number generator.
*
* @param seed
* an int giving the seed
*/
public static void setSeed(int seed) {
// This will also apply to the static method of other distributions
RandomEngine engine = new MersenneTwister(seed);
Uniform.staticSetRandomEngine(engine);
}
@Override
public void runModel() throws IOException {
// Get the occupancy
int[] occupancy = model.getOccupancy();
// Load in the basic data
List<ProbabilityModifier> activities = loadActivityStatistics();
loads = loadAppliances();
// Assign the appliances to households
configure_appliances(loads);
// Simulate each appliance
for (Appliance a : loads) {
// If the appliance is owned, then we simulate it.
// If not, it's already stored an array of empty values
if (a.isOwned()) {
// Initialise the daily simulation loop
int time = 0;
while (time < 1440) {
// Set the default (standby) power demand at this time step
a.power = a.standby_power;
// Get the ten minute period count
int iTenMinuteCount = (int) Math.floor((time - 1) / 10);
// Get the number of current active occupants for this
// minute. Convert from 10 minute to 1 minute resolution
int iActiveOccupants = occupancy[iTenMinuteCount];
// If this appliance is off having completed a cycle (ie. a
// restart delay)
if (a.isOff() && (a.awaitingRestart())) {
// Decrement the cycle time left
a.restart_delay_time_left--;
} else if (a.isOff()) {
// Else if this appliance is off but able to restart
// There must be active occupants, or the profile must
// not depend on occupancy for a start event to occur
if ((iActiveOccupants > 0 && !a.use_profile
.equals("CUSTOM"))
|| (a.use_profile.equals("LEVEL"))) {
// Variable to store the event probability (default
// to 1)
double dActivityProbability = 1;
// For appliances that depend on activity profiles
// and is not a custom profile ...
if ((!a.use_profile.equals("LEVEL"))
&& (!a.use_profile.equals("ACTIVE_OCC"))
&& (!a.use_profile.equals("CUSTOM"))) {
// Get the right activity
ProbabilityModifier pm = getProbabilityModifier(
activities, weekend, iActiveOccupants,
a.use_profile);
// Get the activity statistics for this profile
// at this time step
dActivityProbability = pm.modifiers[iTenMinuteCount];
} else if (a.id.equals("ELEC_SPACE_HEATING")) {
// For electric space heaters ... (excluding
// night storage heaters)
// If this appliance is an electric space
// heater, then activity probability is a
// function of the month of the year
dActivityProbability = oMonthlyRelativeTemperatureModifier[month - 1];
}
// Check the probability of a start event
if (Uniform.staticNextDouble() < (a.calibration * dActivityProbability)) {
// This is a start event
a.start();
// Once it's on, we need to "run" it too
a.run();
}
} else if (a.use_profile.equals("CUSTOM")
&& a.id.equals("STORAGE_HEATER")) {
// Custom appliance handler: storage heaters have a
// simple representation
// The number of cycles (one per day) set out in the
// calibration sheet is used to determine whether
// the storage heater is used
// This model does not account for the changes in
// the Economy 7 time. It assumes that the time
// starts at 00:30 each day
if (iTenMinuteCount == 4) { // ie. 00:30 - 00:40
// Assume January 14th is the coldest day of the
// year
int iMonthOn, iMonthOff;
Calendar cal = GregorianCalendar.getInstance();
cal.set(1997, 1, 14);
// Get the month and day when the storage
// heaters are turned on and off, using the
// number of cycles per year
cal.add(Calendar.DAY_OF_YEAR,
(int) a.cycles_per_year / 2);
iMonthOff = cal.get(Calendar.MONTH);
cal.set(1997, 1, 14);
cal.add(Calendar.DAY_OF_YEAR,
(int) -a.cycles_per_year / 2);
iMonthOn = cal.get(Calendar.MONTH);
// Declare a probability of use variable
double prob;
// If this is a month in which the appliance is
// turned on of off
if ((month == iMonthOff) || (month == iMonthOn)) {
// Pick a 50% chance since this month has
// only a month of year resolution
prob = 0.5 / 10; // (since there are 10
// minutes in this
// period)
} else if ((month > iMonthOff)
&& (month < iMonthOn)) {
// The appliance is not used in summer
prob = 0;
} else {
// The appliance is used in winter
prob = 1;
}
// Determine if a start event occurs
if (Uniform.staticNextDouble() <= prob) {
// This is a start event
a.start();
a.run();
}
}
}
} else {
// The appliance is on - if the occupants become
// inactive, switch off the appliance
if ((iActiveOccupants == 0)
&& (!a.use_profile.equals("LEVEL"))
&& (!a.use_profile.equals("ACT_LAUNDRY"))
&& (!a.use_profile.equals("CUSTOM"))) {
// Do nothing. The activity will be completed upon
// the return of the active occupancy.
// Note that LEVEL means that the appliance use is
// not related to active occupancy.
// Note also that laundry appliances do not switch
// off upon a transition to inactive occupancy.
} else {
a.run();
}
}
// Save the power value
a.consumption[time] = (double) a.power;
// Increment the time
time++;
}
}
}
}
}