/* 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.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import org.apache.commons.io.FileUtils;
import au.com.bytecode.opencsv.CSVWriter;
/**
* Simulates electricity demand for a single UK household
*
* @author James Keirstead
*
*/
public class SimElec {
// Data files
private static String R_DIRECTORY = "/R";
private int month;
private int residents;
private boolean weekend;
private String output_dir;
private boolean runOccupancy = true;
private boolean runLighting = true;
private boolean runAppliances = true;
private boolean makeRPlots = false;
private boolean applianceTotals = false;
private boolean lightingTotals = false;
private boolean grandTotals = true;
private double[] totalConsumption = new double[1440];
/**
* Run the simulation.
*
* @param args
* An array of four String objects. The first entry should be a
* numeral indicating the month of the year (1-12), the second a
* numeral giving the number of residents in the household, the
* third a two-letter code indicating whether to simulate a
* weekend ('we') or weekday ('wd'), and a String giving the
* output directory. An optional fifth argument can be specified,
* an int giving a random number seed. If the arguments are not
* specified, the default simulation is for a two-person
* household on a weekday in January with the files written in
* the current directory.
* @throws IOException
*/
public static void main(String[] args) throws IOException {
int month;
int residents;
boolean weekend;
String output_dir;
if (args.length == 4 || args.length == 5) {
month = Integer.valueOf(args[0]);
residents = Integer.valueOf(args[1]);
weekend = args[2].equals("we") ? true : false;
output_dir = args[3];
if (args.length == 5)
SimElec.setSeed(Integer.valueOf(args[4]));
} else {
System.out.printf(
"%d arguments detected. Using default arguments.%n",
args.length);
month = 1;
residents = 2;
weekend = false;
output_dir = ".";
}
System.out.println("Running SimElec...");
SimElec model = new SimElec(month, residents, weekend, output_dir);
model.run();
System.out.printf("Complete. Results can be found in '%s'%n",
output_dir);
}
/**
* Create a new SimElec model with specified arguments.
*
* @param month
* an int giving the month of the year (1-12)
* @param residents
* an int giving the number of residents in the household (1-5)
* @param weekend
* a boolean indicating whether to simulating a weekend (
* <code>true</code>) or weekday (<code>false</code>)
* @param output_dir
* a String giving the output directory
*/
public SimElec(int month, int residents, boolean weekend, String output_dir) {
// Set the inputs cleaning as necessary
this.month = validateMonth(month);
this.residents = validateResidents(residents);
this.weekend = weekend;
this.output_dir = output_dir;
}
/**
* Validates a specified month.
*
* @param month
* an int specifying the month (1-12)
* @return the specified month is a valid month, or 1 if not.
*/
public static int validateMonth(int month) {
if (month >= 1 && month <= 12) {
return (month);
} else {
System.out
.printf("Invalid month %d specified. Defaulting to 1 (January).%n",
month);
return (1);
}
}
/**
* Validate the number of residents to simulation.
*
* @param residents
* an int giving the number of residents to simulate
*
* @return the specified value if >=1 and <=5. If greater than 5, returns 5.
* If less than 1, returns 1.
*/
public static int validateResidents(int residents) {
if (residents >= 1 && residents <= 5) {
return (residents);
} else if (residents < 1) {
System.out
.printf("%d residents specified, only 1 to 5 supported. Defaulting to 1.%n",
residents);
return (1);
} else {
System.out
.printf("%d residents specified, only 1 to 5 supported. Defaulting to 5.%n",
residents);
return (5);
}
}
/**
* Runs the simulation.
*
* @throws IOException
*/
public void run() throws IOException {
OccupancyModel occ = new OccupancyModel(residents, weekend, output_dir);
if (runOccupancy) {
occ.run();
}
if (runLighting) {
LightingModel lights = new LightingModel(month, output_dir, occ);
lights.setTotalsOnly(lightingTotals);
lights.run();
if (grandTotals) {
totalConsumption = addArrays(totalConsumption,
lights.getTotalConsumption());
}
}
if (runAppliances) {
ApplianceModel appliances = new ApplianceModel(month, weekend,
output_dir, occ);
appliances.setTotalsOnly(applianceTotals);
appliances.run();
if (grandTotals) {
totalConsumption = addArrays(totalConsumption,
appliances.getTotalConsumption());
}
}
if (grandTotals) {
// Build the results array (only one line)
ArrayList<String[]> results = new ArrayList<String[]>(1);
String[] totalString = Load.buildExportString("TOTAL",
totalConsumption);
results.add(totalString);
// Write the data to a file
File file = new File(output_dir, "totals.csv");
CSVWriter writer = new CSVWriter(new FileWriter(file), ',', '\0');
writer.writeAll(results);
writer.close();
}
if (makeRPlots) {
try {
makeRPlots();
} catch (Exception e) {
System.out.println("Unable to create R plots.");
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
/**
* Performs an element-wise addition of two arrays
*
* @param a
* the first array
* @param b
* the second array
* @return the sum array
*
* @throws IllegalArgumentException
* if the arrays are not of equal length
*/
private static double[] addArrays(double[] a, double[] b) {
if (a.length != b.length) {
throw new IllegalArgumentException("Arrays must be of equal length");
}
double[] c = new double[a.length];
for (int i = 0; i < c.length; i++) {
c[i] = a[i] + b[i];
}
return c;
}
/**
* Runs an R script to generate a summary plot
*
* @throws IOException
* @throws InterruptedException
*
*/
private void makeRPlots() throws IOException, InterruptedException {
// First copy the scripts to the working directory
File destDir = new File(output_dir, "tmpR");
if (!destDir.exists())
destDir.mkdirs();
String fileName;
InputStream is = getClass().getResourceAsStream(
R_DIRECTORY.concat("/index.txt"));
BufferedReader br = new BufferedReader(new InputStreamReader(is));
while ((fileName = br.readLine()) != null) {
InputStream is2 = getClass().getResourceAsStream(
R_DIRECTORY.concat("/").concat(fileName));
File outFile = new File(destDir, fileName);
FileUtils.copyInputStreamToFile(is2, outFile);
is2.close();
}
br.close();
// Then figure out where the data and results should go
/*
* The awkward construction is a hack to ensure that d: and d:\ behave
* the same
*/
File dataDir = new File(output_dir, ".").getCanonicalFile();
File outputFile = new File(output_dir, "simelec.png");
// Then run the scripts
String cmd = String.format("Rscript make-summary-plot.r \"%s\" \"%s\"",
dataDir, outputFile.getCanonicalPath());
Process p;
StringBuffer output = new StringBuffer();
try {
p = Runtime.getRuntime().exec(cmd, null, destDir);
p.waitFor();
/*
* Not going to do anything with this output at the moment
*/
BufferedReader reader = new BufferedReader(new InputStreamReader(
p.getInputStream()));
String line = "";
while ((line = reader.readLine()) != null) {
output.append(line + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
// Then tidy up
FileUtils.deleteDirectory(destDir);
}
/**
* Sets the seed for the random number generator used by SimElec.
*
* @param seed
* an int giving the seed
*/
public static void setSeed(int seed) {
DiscretePDF.setSeed(seed);
}
/**
* Set whether to run the Appliance simulation
*
* @param run
*/
public void setRunAppliances(boolean run) {
this.runAppliances = run;
}
/**
* Set whether to run the Lighting simulation
*
* @param run
*/
public void setRunLighting(boolean run) {
this.runLighting = run;
}
/**
* Set whether to make the R plots
*
* @param makePlots
* a boolean indicating if the plots should be made
*/
public void setMakeRPlots(boolean makePlots) {
this.makeRPlots = makePlots;
}
/**
* Set whether to calculate only the total loads for the appliance model.
*
* @param total
* a boolean indicating if only the total appliance loads should
* be reported
*/
public void setAppliancesTotalsOnly(boolean total) {
this.applianceTotals = total;
}
/**
* Set whether to calculate only the total loads for the lighting model.
*
* @param total
* a boolean indicating if only the total lighting loads should
* be reported
*/
public void setLightingTotalsOnly(boolean total) {
this.lightingTotals = total;
}
/**
* Set whether to calculate the grand total of all load models.
*
* @param total
* a boolean indicating if the grand totals should be reported
*/
public void setCalculateGrandTotals(boolean total) {
this.grandTotals = total;
}
/**
* Set whether to run the occupancy simulation. If this is set to false,
* then you must provide the file <code>occupancy_output.csv</code> in the
* output directory.
*
* @param run
* should the occupancy model be run?
*
* @throws FileNotFoundException
* if occupancy output file is not found
*/
public void setRunOccupancy(boolean run) throws FileNotFoundException {
/*
* If we're turning off the occupancy model, then we have to ensure that
* we already have an output file ready to process.
*/
if (!run) {
File f = OccupancyModel.getOutputFile(output_dir);
if (!f.exists()) {
String msg = String.format(
"Occupancy model output file '%s' not found.",
f.toString());
throw new FileNotFoundException(msg);
}
}
this.runOccupancy = run;
}
/**
* Gets the total load profile from this SimElec simulation.
*
* @return an array of length 1440 giving minute-by-minute electricity loads
* (W)
*/
public double[] getGrandTotals() {
return totalConsumption;
}
}