jeplus.JEPlusProject.java Source code

Java tutorial

Introduction

Here is the source code for jeplus.JEPlusProject.java

Source

/***************************************************************************
 *   jEPlus - EnergyPlus shell for parametric studies                      *
 *   Copyright (C) 2010  Yi Zhang <yi@jeplus.org>                          *
 *                                                                         *
 *   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 jeplus;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.swing.tree.DefaultMutableTreeNode;
import jeplus.data.ExecutionOptions;
import jeplus.data.ParameterItem;
import jeplus.data.RVX;
import jeplus.data.RandomSource;
import jeplus.data.RouletteWheel;
import jeplus.util.CsvUtil;
import jeplus.util.RelativeDirUtil;
import org.apache.commons.math3.distribution.ExponentialDistribution;
import org.apache.commons.math3.distribution.LogNormalDistribution;
import org.apache.commons.math3.distribution.NormalDistribution;
import org.apache.commons.math3.distribution.TriangularDistribution;
import org.apache.commons.math3.random.SobolSequenceGenerator;
import org.slf4j.LoggerFactory;

/**
 * JEPlus Project class encapsulates definition of a project
 * @author Yi Zhang
 * @version 1.0
 * @since 1.0
 */
public class JEPlusProject implements Serializable {

    /** Logger */
    final static org.slf4j.Logger logger = LoggerFactory.getLogger(JEPlusProject.class);

    /** ScriptEngine used by all evaluators */
    protected static final ScriptEngine Script_Engine = new ScriptEngineManager().getEngineByName("python");
    static {
        // Set up script engine
        try {
            Script_Engine.eval("import math");
        } catch (ScriptException sce) {
            logger.error("Script engine error when importing math module.", sce);
        }
    }

    public static ScriptEngine getScript_Engine() {
        return Script_Engine;
    }

    private static final long serialVersionUID = -3920321004466467177L;
    public static final int EPLUS = 0;
    public static final int TRNSYS = 1;
    public static final int INSEL = 2;
    public static final int EP2TRN = 99;

    /** This is the working directory of the program */
    protected static String UserBaseDir = System.getProperty("user.dir") + File.separator;

    /** Flag marking whether this project has been changed since last save/load */
    transient private boolean ContentChanged = false;

    /** Base directory of the project, i.e. the location where the project file is saved */
    protected String BaseDir = null;

    /** Project Type: E+ or TRNSYS */
    protected int ProjectType = -1; // set to illegal type

    /** Project ID string */
    protected String ProjectID = null;

    /** Project notes string */
    protected String ProjectNotes = null;

    /** Local directory for IDF template files */
    protected String IDFDir = null;
    /** Template file to be used in this job; or a (';' delimited) list of files for the batch project */
    protected String IDFTemplate = null;

    /** Local directory for weather files */
    protected String WeatherDir = null;
    /** Weather file to be used in this job; or a (';' delimited) list of files for the batch project */
    protected String WeatherFile = null;

    /** Flag for calling ReadVarsESO or not */
    protected boolean UseReadVars = false;

    /** ReadVarsESO configure file to be used to extract results */
    protected String RVIDir = null;
    /** ReadVarsESO configure file to be used to extract results */
    protected String RVIFile = null;

    /** Local directory for DCK/TRD (for TRNSYS) template files */
    protected String DCKDir = null;
    /** Template file to be used in this job; or a (';' delimited) list of files for the batch project */
    protected String DCKTemplate = null;
    /** Output file names that contain results for each simulation; used for TRNSYS */
    protected String OutputFileNames = null;

    /** Local directory for INSEL (for INSEL) template files */
    protected String INSELDir = null;
    /** Template file to be used in this job; or a (';' delimited) list of files for the batch project */
    protected String INSELTemplate = null;

    /** Execution settings */
    protected ExecutionOptions ExecSettings = null;

    /** List of parameters */
    protected ArrayList<ParameterItem> Parameters = null;

    /** Parameter tree */
    protected DefaultMutableTreeNode ParamTree = null;

    /** Parameter definition file */
    protected String ParamFile = null;

    /** RVX object for result collection */
    protected RVX Rvx = null;

    /** External file defining the RVX object, in JSON format */
    protected String RvxFile = null;

    /**
     * Class containing post-process function options
     */
    public class PostProcOptions {
        protected String ExtraRVIFile = "./my.rvi";
        protected String ExtraRVIFrequency = "RunPeriod";
        protected String SelectedTRNOutput = "TRNSYSout.csv";
        protected boolean DeleteOutput = false;

        protected String PostProcFunctor = "DefaultPostProcFunc";
        protected String ReferenceCase = null;
        protected String ReferenceTable = "reference.csv";

        protected String ExportDir = "./";
        protected boolean ExportJobTables = false;
        protected String JobTablePrefix = "my";
        protected boolean ExportOneTable = true;
        protected String OneTableName = "processed_result";
        protected boolean ExportStatsTables = true;
        protected String StatsTablePrefix = "my";

        public PostProcOptions() {
        }
    }

    /**
     * Default constructor
     */
    public JEPlusProject() {
        ProjectType = EPLUS;
        ProjectID = "G";
        ProjectNotes = "New project";
        IDFDir = "./";
        // IDFTemplate = "select files ...";
        WeatherDir = "./";
        // WeatherFile = "select files ...";
        UseReadVars = true;
        RVIDir = "./";
        // RVIFile = "select a file ...";
        DCKDir = "./";
        // DCKTemplate = "select a file ...";
        INSELDir = "./";
        // INSELTemplate = "select a file ...";
        OutputFileNames = "trnsysout.csv"; // fixed on one file name for the time being
        ExecSettings = new ExecutionOptions();
        ParameterItem root = new ParameterItem(this);
        Parameters = new ArrayList<>();
        Parameters.add(root);
        ParamTree = new DefaultMutableTreeNode(root);
        BaseDir = new File("./").getAbsolutePath() + File.separator;
    }

    /**
     * Cloning constructor. New project state is set to 'changed' after cloning
     * @param proj Project object to be cloned
     */
    public JEPlusProject(JEPlusProject proj) {
        this();
        if (proj != null) {
            ContentChanged = true; // set content changed for the new project obj
            BaseDir = proj.BaseDir;
            ProjectType = proj.ProjectType;
            ProjectID = proj.ProjectID;
            ProjectNotes = proj.ProjectNotes;
            IDFDir = proj.IDFDir;
            IDFTemplate = proj.IDFTemplate;
            WeatherDir = proj.WeatherDir;
            WeatherFile = proj.WeatherFile;
            UseReadVars = proj.UseReadVars;
            RVIDir = proj.RVIDir;
            RVIFile = proj.RVIFile;
            DCKDir = proj.DCKDir;
            DCKTemplate = proj.DCKTemplate;
            INSELDir = proj.INSELDir;
            INSELTemplate = proj.INSELTemplate;
            OutputFileNames = proj.OutputFileNames;
            ExecSettings = new ExecutionOptions(proj.ExecSettings);
            Parameters = proj.Parameters;
            ParamTree = proj.ParamTree;
            Rvx = proj.Rvx;
        }
    }

    // ================= File operations ==============================
    /**
     * Save the project to an object file
     * @param fn The File object associated with the file to which the contents will be saved
     * @param proj The project object to be serialised
     * @return Successful or not
     */
    public static boolean serialize(File fn, JEPlusProject proj) {
        boolean success = true;
        try (ObjectOutputStream ow = new ObjectOutputStream(new FileOutputStream(fn))) {
            ow.writeObject(proj);
        } catch (IOException ioe) {
            logger.error("Error writing project object to " + fn, ioe);
            success = false;
        }
        return success;
    }

    /**
     * Read parameter tree from an object file
     * @param fn The File object associated with the file
     * @return de-serialised object
     */
    public static JEPlusProject deserialize(File fn) {
        JEPlusProject proj = null;
        try (ObjectInputStream or = new ObjectInputStream(new FileInputStream(fn))) {
            proj = (JEPlusProject) or.readObject();
        } catch (IOException ioe) {
            logger.error("Error reading project object from " + fn, ioe);
        } catch (Exception e) {
            logger.error("Error parsing project object from " + fn, e);
        }
        return proj;
    }

    /**
     * Save this project to an XML file
     * @param fn The File object associated with the file to which the contents will be saved
     * @return Successful or not
     */
    public boolean saveAsXML(File fn) {
        boolean success = true;

        // Write project file
        XMLEncoder encoder;
        try {
            encoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream(fn)));
            // Clear external parameters and rvx file reference fields before saving the project
            // These files are for importing only
            this.ParamFile = null;
            this.RvxFile = null;
            encoder.writeObject(this);
            encoder.close();
            // get new location of project file
            String dir = fn.getAbsoluteFile().getParent();
            dir = dir.concat(dir.endsWith(File.separator) ? "" : File.separator);
            this.updateBaseDir(dir);
            this.ContentChanged = false;
        } catch (FileNotFoundException ex) {
            logger.error("Failed to create " + fn + " for writing project.", ex);
            success = false;
        }
        return success;
    }

    /**
     * Read a project from an XML file. The members of this project are not updated.
     * @param fn The File object associated with the file
     * @return a new project instance from the file
     */
    public static JEPlusProject loadAsXML(File fn) {
        JEPlusProject proj;
        try (XMLDecoder decoder = new XMLDecoder(new BufferedInputStream(new FileInputStream(fn)))) {
            proj = ((JEPlusProject) decoder.readObject());
        } catch (Exception ex) {
            logger.error("Error loading project from file " + fn, ex);
            return null;
        }
        String dir = fn.getAbsoluteFile().getParent();
        dir = dir.concat(dir.endsWith(File.separator) ? "" : File.separator);
        // proj.updateBaseDir(dir);
        proj.setBaseDir(dir);
        if (proj.ParamFile != null) {
            // Load parameters from text file
            proj.importParameterTableFile(new File(RelativeDirUtil.checkAbsolutePath(proj.ParamFile, dir)));
        } else {
            // Reassign reference to project in all parameters
            if (proj.getParamTree() != null) {
                Enumeration params = proj.getParamTree().breadthFirstEnumeration();
                while (params.hasMoreElements()) {
                    ((ParameterItem) ((DefaultMutableTreeNode) params.nextElement()).getUserObject())
                            .setProject(proj);
                }
            }
        }
        // Assign the first branch to the Parameters list
        DefaultMutableTreeNode thisleaf = proj.getParamTree().getFirstLeaf();
        Object[] path = thisleaf.getUserObjectPath();
        proj.setParameters(new ArrayList<ParameterItem>());
        for (Object item : path) {
            proj.getParameters().add((ParameterItem) item);
        }
        // Load Rvx if a RVX file is available
        try {
            proj.Rvx = RVX.getRVX(proj.resolveRVIDir() + proj.getRVIFile());
        } catch (IOException ioe) {
            logger.error("Cannot read the project's RVX file", ioe);
        }

        // done            
        return proj;
    }

    /**
     * Save this project to an XML file
     * @param file The File object associated with the file to which the contents will be saved
     * @return Successful or not
     */
    public boolean saveAsJSON(File file) {
        boolean success = true;
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDateFormat(format);
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        try (FileOutputStream fw = new FileOutputStream(file);) {
            mapper.writeValue(fw, this);
            logger.info("Project saved to " + file.getAbsolutePath());
        } catch (Exception ex) {
            logger.error("Error saving project to JSON.", ex);
            success = false;
        }
        return success;
    }

    /**
     * Read the project from the given JSON file. 
     * @param file The File object associated with the file
     * @return a new project instance from the file
     * @throws java.io.IOException
     */
    public static JEPlusProject loadFromJSON(File file) throws IOException {
        // Read JSON
        ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally
        JEPlusProject project = mapper.readValue(file, JEPlusProject.class);
        // Set base dir
        String dir = file.getAbsoluteFile().getParent();
        dir = dir.concat(dir.endsWith(File.separator) ? "" : File.separator);
        // project.updateBaseDir(dir);
        project.setBaseDir(dir);
        // If parameter file is given, use the contents to override the parameters in the project
        if (project.ParamFile != null) {
            // Load parameters from text file, to replace the existing Parameters list and tree
            project.importParameterTableFile(new File(RelativeDirUtil.checkAbsolutePath(project.ParamFile, dir)));
        } else {
            // Reassign reference to project in all parameters, and build param tree
            if (project.getParameters() != null) {
                for (ParameterItem item : project.getParameters()) {
                    item.setProject(project);
                }
                project.addParameterListAsBranch(null, project.getParameters());
            }
        }
        // If external RVX file is specified, use its contents for Rvx object
        if (project.RvxFile != null) {
            try {
                project.Rvx = RVX.getRVX(RelativeDirUtil.checkAbsolutePath(project.RvxFile, dir));
            } catch (IOException ioe) {
                logger.error("Cannot read the given RVX file", ioe);
            }
        }
        project.ContentChanged = false;
        // Return
        return project;
    }

    // ================== Getters and Setters ==========================

    /**
     * Get the base directory of the current project
     * @return Base directory
     */
    @JsonIgnore
    public String getBaseDir() {
        return BaseDir;
    }

    /**
     * Set the base directory of the current project to the given paths. This 
     * function is for serialisation only. For updating the base dir, use updateBaseDir()
     * @param BaseDir The new base directory for this project
     */
    @JsonIgnore
    public void setBaseDir(String BaseDir) {
        this.BaseDir = BaseDir;
    }

    public int getProjectType() {
        return ProjectType;
    }

    public void setProjectType(int ProjectType) {
        this.ProjectType = ProjectType;
    }

    public String getProjectID() {
        return ProjectID;
    }

    public void setProjectID(String ProjectID) {
        this.ProjectID = ProjectID;
    }

    public String getProjectNotes() {
        return ProjectNotes;
    }

    public void setProjectNotes(String ProjectNotes) {
        this.ProjectNotes = ProjectNotes;
    }

    public ExecutionOptions getExecSettings() {
        return ExecSettings;
    }

    public void setExecSettings(ExecutionOptions ExecSettings) {
        this.ExecSettings = ExecSettings;
    }

    public String getIDFDir() {
        return IDFDir;
    }

    public void setIDFDir(String IDFDir) {
        this.IDFDir = IDFDir;
    }

    public String getIDFTemplate() {
        return IDFTemplate;
    }

    public void setIDFTemplate(String IDFTemplate) {
        this.IDFTemplate = IDFTemplate;
    }

    @JsonIgnore
    public boolean isContentChanged() {
        return ContentChanged;
    }

    @JsonIgnore
    public void setContentChanged(boolean ContentChanged) {
        this.ContentChanged = ContentChanged;
    }

    public ArrayList<ParameterItem> getParameters() {
        return Parameters;
    }

    public void setParameters(ArrayList<ParameterItem> Parameters) {
        this.Parameters = Parameters;
    }

    @JsonIgnore
    public DefaultMutableTreeNode getParamTree() {
        return ParamTree;
    }

    @JsonIgnore
    public void setParamTree(DefaultMutableTreeNode ParamTree) {
        this.ParamTree = ParamTree;
    }

    @JsonIgnore
    public String getRVIDir() {
        return RVIDir;
    }

    @JsonIgnore
    public void setRVIDir(String RVIDir) {
        this.RVIDir = RVIDir;
    }

    @JsonIgnore
    public String getRVIFile() {
        return RVIFile;
    }

    @JsonIgnore
    public void setRVIFile(String RVIFile) {
        this.RVIFile = RVIFile;
    }

    @JsonIgnore
    public boolean isUseReadVars() {
        return UseReadVars;
    }

    @JsonIgnore
    public void setUseReadVars(boolean UseReadVars) {
        this.UseReadVars = UseReadVars;
    }

    @JsonIgnore
    public String getDCKDir() {
        return DCKDir;
    }

    @JsonIgnore
    public void setDCKDir(String DCKDir) {
        this.DCKDir = DCKDir;
    }

    @JsonIgnore
    public String getDCKTemplate() {
        return DCKTemplate;
    }

    @JsonIgnore
    public void setDCKTemplate(String DCKTemplate) {
        this.DCKTemplate = DCKTemplate;
    }

    @JsonIgnore
    public String getINSELDir() {
        return INSELDir;
    }

    @JsonIgnore
    public void setINSELDir(String INSELDir) {
        this.INSELDir = INSELDir;
    }

    @JsonIgnore
    public String getINSELTemplate() {
        return INSELTemplate;
    }

    @JsonIgnore
    public void setINSELTemplate(String INSELTemplate) {
        this.INSELTemplate = INSELTemplate;
    }

    @JsonIgnore
    public String getOutputFileNames() {
        return OutputFileNames;
    }

    @JsonIgnore
    public void setOutputFileNames(String OutputFileNames) {
        this.OutputFileNames = OutputFileNames;
    }

    public String getWeatherDir() {
        return WeatherDir;
    }

    public void setWeatherDir(String WeatherDir) {
        this.WeatherDir = WeatherDir;
    }

    public String getWeatherFile() {
        return WeatherFile;
    }

    public void setWeatherFile(String WeatherFile) {
        this.WeatherFile = WeatherFile;
    }

    public String getParamFile() {
        return ParamFile;
    }

    public void setParamFile(String ParamFile) {
        this.ParamFile = ParamFile;
    }

    public RVX getRvx() {
        return Rvx;
    }

    public void setRvx(RVX Rvx) {
        this.Rvx = Rvx;
    }

    public String getRvxFile() {
        return RvxFile;
    }

    public void setRvxFile(String RvxFile) {
        this.RvxFile = RvxFile;
        // If external RVX file is specified, use its contents for Rvx object
        if (RvxFile != null) {
            try {
                this.Rvx = RVX.getRVX(RelativeDirUtil.checkAbsolutePath(RvxFile, this.getBaseDir()));
            } catch (IOException ioe) {
                logger.error("Cannot read the given RVX file", ioe);
            }
        }
    }

    // ====================== End Getters and Setters ======================

    // A new set of resolveXYZFile functions

    /**
     * Set the base directory of the current project to the given paths. Once the
     * new paths are set, the relative paths of all project files are recalculated,
     * and the absolute paths converted to relative form.
     * @param BaseDir The new base directory for this project
     */
    public void updateBaseDir(String BaseDir) {
        // First to convert all paths to absolute using the existing Base
        this.setWeatherDir(this.resolveWeatherDir()); // Weather file path
        this.setIDFDir(this.resolveIDFDir()); // idf file path
        this.setDCKDir(this.resolveDCKDir()); // dck file path
        this.setRVIDir(this.resolveRVIDir()); // rvi file path
        this.getExecSettings().setParentDir(this.resolveWorkDir()); // output dir
        // Update BaseDir
        this.BaseDir = BaseDir;
        // Calculate relative dir from the new base
        this.setWeatherDir(RelativeDirUtil.getRelativePath(this.getWeatherDir(), this.BaseDir, "/")); // Weather file path
        this.setIDFDir(RelativeDirUtil.getRelativePath(this.getIDFDir(), this.BaseDir, "/")); // idf file path
        this.setDCKDir(RelativeDirUtil.getRelativePath(this.getDCKDir(), this.BaseDir, "/")); // dck file path
        this.setRVIDir(RelativeDirUtil.getRelativePath(this.getRVIDir(), this.BaseDir, "/")); // rvi file path
        this.getExecSettings().setParentDir(
                RelativeDirUtil.getRelativePath(this.getExecSettings().getParentDir(), this.BaseDir, "/")); // output dir
    }

    /** 
     * Resolve the path to the project's work (a.k.a. parent) directory. If
     * relative path is used, it is relative to the project folder
     * @return Resolved absolute paths
     */
    public String resolveWorkDir() {
        String dir = RelativeDirUtil.checkAbsolutePath(ExecSettings.getWorkDir(), BaseDir);
        dir = dir.concat(dir.endsWith(File.separator) ? "" : File.separator);
        return dir;
    }

    /** 
     * Resolve the path to the PBS script to use for running this project. If
     * relative path is used, it is relative to the UserBaseDir rather than
     * the project folder
     * @return Resolved absolute paths
     */
    public String resolvePBSscriptFile() {
        return RelativeDirUtil.checkAbsolutePath(ExecSettings.getPBSscriptFile(), UserBaseDir);
    }

    /** 
     * Resolve the path to the server config file for running this project. If
     * relative path is used, it is relative to the UserBaseDir rather than
     * the project folder
     * @return Resolved absolute paths
     */
    public String resolveServerConfigFile() {
        return RelativeDirUtil.checkAbsolutePath(ExecSettings.getServerConfigFile(), UserBaseDir);
    }

    /** 
     * Resolve the path to the IDF models of this project. If
     * relative path is used, it is relative to the project folder
     * @return Resolved absolute paths
     */
    public String resolveIDFDir() {
        String dir = RelativeDirUtil.checkAbsolutePath(this.getIDFDir(), BaseDir);
        dir = dir.concat(dir.endsWith(File.separator) ? "" : File.separator);
        return dir;
    }

    /** 
     * Resolve the path to the RVI file of this project. If
     * relative path is used, it is relative to the project folder
     * @return Resolved absolute paths
     */
    public String resolveRVIDir() {
        String dir = RelativeDirUtil.checkAbsolutePath(this.getRVIDir(), BaseDir);
        dir = dir.concat(dir.endsWith(File.separator) ? "" : File.separator);
        return dir;
    }

    /** 
     * Resolve the path to the weather files of this project. If
     * relative path is used, it is relative to the project folder
     * @return Resolved absolute paths
     */
    public String resolveWeatherDir() {
        String dir = RelativeDirUtil.checkAbsolutePath(this.getWeatherDir(), BaseDir);
        dir = dir.concat(dir.endsWith(File.separator) ? "" : File.separator);
        return dir;
    }

    /** 
     * Resolve the path to the RVI file of this project. If
     * relative path is used, it is relative to the project folder
     * @return Resolved absolute paths
     */
    public String resolveDCKDir() {
        String dir = RelativeDirUtil.checkAbsolutePath(this.getDCKDir(), BaseDir);
        dir = dir.concat(dir.endsWith(File.separator) ? "" : File.separator);
        return dir;
    }

    /** 
     * Resolve the path to the RVI file of this project. If
     * relative path is used, it is relative to the project folder
     * @return Resolved absolute paths
     */
    public String resolveINSELDir() {
        String dir = RelativeDirUtil.checkAbsolutePath(this.getINSELDir(), BaseDir);
        dir = dir.concat(dir.endsWith(File.separator) ? "" : File.separator);
        return dir;
    }

    /**
     * This function reads the E+ version from the first model file, and return it in a string, such as 7.0
     * @return Version info in a string
     */
    @JsonIgnore
    public String getEPlusModelVersion() {
        return IDFmodel.getEPlusVersionInIDF(
                resolveIDFDir() + parseFileListString(resolveIDFDir(), getIDFTemplate()).get(0));
    }

    /**
     * This function copies information from an EPlusWorkEnv object to provide
     * some backwards compatibility
     * @param env the EPlusWorkEnv object
     */
    public void copyFromEnv(EPlusWorkEnv env) {
        IDFDir = env.IDFDir;
        IDFTemplate = env.IDFTemplate;
        WeatherDir = env.WeatherDir;
        WeatherFile = env.WeatherFile;
        UseReadVars = env.UseReadVars;
        RVIDir = env.RVIDir;
        RVIFile = env.RVIFile;
        ProjectType = env.ProjectType;
        DCKDir = env.DCKDir;
        DCKTemplate = env.DCKTemplate;
        INSELDir = env.INSELDir;
        INSELTemplate = env.INSELTemplate;
        OutputFileNames = env.OutputFileNames;
        ExecSettings.setParentDir(env.ParentDir);
        ExecSettings.setKeepEPlusFiles(env.KeepEPlusFiles);
        ExecSettings.setKeepJEPlusFiles(env.KeepJEPlusFiles);
        ExecSettings.setKeepJobDir(env.KeepJobDir);
        ExecSettings.setDeleteSelectedFiles(env.SelectedFiles != null);
        ExecSettings.setSelectedFiles(env.SelectedFiles);
        ExecSettings.setRerunAll(env.ForceRerun);
    }

    /**
     * This function copies information to an EPlusWorkEnv object to provide
     * some backwards compatibility
     * @param env the EPlusWorkEnv object
     */
    public void resolveToEnv(EPlusWorkEnv env) {
        env.IDFDir = this.resolveIDFDir();
        env.IDFTemplate = IDFTemplate;
        env.WeatherDir = this.resolveWeatherDir();
        env.WeatherFile = WeatherFile;
        env.UseReadVars = UseReadVars;
        env.RVIDir = this.resolveRVIDir();
        env.RVIFile = RVIFile;
        env.ProjectType = ProjectType;
        env.DCKDir = this.resolveDCKDir();
        env.DCKTemplate = DCKTemplate;
        env.INSELDir = this.resolveINSELDir();
        env.INSELTemplate = INSELTemplate;
        env.OutputFileNames = OutputFileNames;
        env.ProjectBaseDir = this.BaseDir;
        env.ParentDir = this.resolveWorkDir();
        env.KeepEPlusFiles = ExecSettings.isKeepEPlusFiles();
        env.KeepJEPlusFiles = ExecSettings.isKeepJEPlusFiles();
        env.KeepJobDir = ExecSettings.isKeepJobDir();
        env.SelectedFiles = ExecSettings.isDeleteSelectedFiles() ? ExecSettings.getSelectedFiles() : null;
        env.ForceRerun = ExecSettings.isRerunAll();
    }

    /**
     * Decode IDF or Weather files string and store them, with directory, in an array
     * @param dir Default directory for IDF/IMF/EPW/LST files. Entries in the LST files should contain only relative paths to this directory
     * @param files Input files string. ';' delimited list of IDF/IMF/EPW/LST files
     * @return Validation result: true if all files are available
     */
    public ArrayList<String> parseFileListString(String dir, String files) {
        ArrayList<String> Files = new ArrayList<>();
        if (files != null) {
            String[] file = files.split("\\s*;\\s*");
            for (int i = 0; i < file.length; i++) {
                if (file[i].length() > 0) {
                    // If a list file, parse it
                    if (file[i].toLowerCase().endsWith(".lst")) {
                        Files.addAll(parseListFile(dir, file[i]));
                        // otherwise, just add
                    } else {
                        Files.add(file[i]);
                    }
                }
            }
        }
        return Files;
    }

    /**
     * Get all input files in the project. This function is for E+ version conversion and possibly auto project compilation 
     * for remote execution. In a jEPlus project, the following files will be listed:
     * - Weather files (not for version conversion)
     * - IDF/IMF models 
     * - Include files in IMF models. Actual file name will be identified if include files are used as parameters
     * - RVI/MVI file
     * @return A list of file full paths
     */
    @JsonIgnore
    public ArrayList<String> getAllInputFiles() {
        ArrayList<String> filelist = new ArrayList<>();
        if (ProjectType == EPLUS) {

        } else if (ProjectType == TRNSYS) {

        } else if (ProjectType == INSEL) {

        }
        return filelist;
    }

    /**
     * Convert all directories to relative paths to where the project base (the
     * location of the project file, for example) is.
     * @param Base The base directory of the project
     * @return conversion successful or not
     */
    protected boolean convertToRelativeDir(File Base) {
        if (Base != null && Base.exists()) {
            File idf = new File(IDFDir);
            File wthr = new File(WeatherDir);
            File rvi = new File(RVIDir);
            File out = new File(ExecSettings.getWorkDir());
            if (idf.exists() && wthr.exists() && rvi.exists() && out.exists()) {
                IDFDir = RelativeDirUtil.getRelativePath(Base, idf);
                WeatherDir = RelativeDirUtil.getRelativePath(Base, wthr);
                RVIDir = RelativeDirUtil.getRelativePath(Base, rvi);
                ExecSettings.setParentDir(RelativeDirUtil.getRelativePath(Base, out));
                return true;
            }
        }
        return false;
    }

    /**
     * Convert all directories to absolute paths.
     * @param base The base directory of the project
     */
    protected void convertToAbsoluteDir(File base) {
        IDFDir = new File(base, IDFDir).getAbsolutePath();
        WeatherDir = new File(base, WeatherDir).getAbsolutePath();
        RVIDir = new File(base, RVIDir).getAbsolutePath();
        ExecSettings.setParentDir(new File(base, ExecSettings.getWorkDir()).getAbsolutePath());
        //ExecSettings.setPBSscriptFile(new File (base, ExecSettings.getPBSscriptFile()).getAbsolutePath());
        //ExecSettings.setServerConfigFile(new File (base, ExecSettings.getServerConfigFile()).getAbsolutePath());
    }

    /**
     * Get a list of search strings from the parameter tree of this project.
     *
     * @return
     */
    @JsonIgnore
    public String[] getSearchStrings() {
        DefaultMutableTreeNode ParaTree = this.getParamTree();
        if (ParaTree == null)
            return null;

        ArrayList<String> SearchStrings = new ArrayList<>();
        Enumeration nodes = ParaTree.preorderEnumeration();
        while (nodes.hasMoreElements()) {
            Object node = nodes.nextElement();
            String ss = ((ParameterItem) ((DefaultMutableTreeNode) node).getUserObject()).getSearchString();
            if (ss != null && ss.trim().length() > 0 && !SearchStrings.contains(ss)) {
                SearchStrings.add(ss);
            }
        }
        return SearchStrings.toArray(new String[0]);
    }

    /**
     * Get the total number of parameters in the parameter tree
     *
     * @return parameter count
     */
    @JsonIgnore
    public int getNumberOfParams() {
        DefaultMutableTreeNode ParaTree = this.getParamTree();
        if (ParaTree == null) {
            return 0;
        }
        Enumeration nodes = ParaTree.preorderEnumeration();
        int count = 0;
        while (nodes.hasMoreElements()) {
            nodes.nextElement();
            count++;
        }
        return count;
    }

    /**
     * Parse the list file (for models or weathers) and return result in an
     * ArrayList. The format of a list file must be one input file in each line.
     * "#" and "!" can be used for comment lines.
     * @param dir Directory of the list file
     * @param fn File name
     * @return File list in an List
     */
    protected ArrayList<String> parseListFile(String dir, String fn) {
        ArrayList<String> list = new ArrayList<>();
        try (BufferedReader fr = new BufferedReader(new FileReader(dir + fn))) {
            String line = fr.readLine();
            while (line != null) {
                if (line.contains("#"))
                    line = line.substring(0, line.indexOf("#")).trim();
                if (line.contains("!"))
                    line = line.substring(0, line.indexOf("!")).trim();
                if (line.length() > 0)
                    list.add(line);
                line = fr.readLine();
            }
            fr.close();
        } catch (Exception ex) {
            logger.error("Error reading from list file " + dir + fn, ex);
        }
        return list;
    }

    /**
     * Import parameters in a CSV table (#-commented) and create a new single-branch tree
     * @param file File name of the table
     * @return import successful or not
     */
    public boolean importParameterTableFile(File file) {
        String[][] table = CsvUtil.parseCSVwithComments(file);
        if (table != null) {
            Parameters = new ArrayList<>();
            for (String[] row : table) {
                if (row.length >= 8) {
                    Parameters.add(new ParameterItem(this, row));
                }
            }
            addParameterListAsBranch(null, Parameters);
            return true;
        }
        return false;
    }

    /**
     * Import parameters in a CSV table (#-commented) and create a new single-branch tree
     * @param file File name of the table
     * @return import successful or not
     */
    public boolean exportParameterTableFile(File file) {
        // Get all parameters in the first branch
        if (ParamTree != null) {
            try (PrintWriter fw = new PrintWriter(new FileWriter(file))) {
                fw.println("# Parameter list for project: " + this.getProjectID() + " (exported at "
                        + new SimpleDateFormat().format(new Date()) + ")");
                fw.println("# Note: this list contains only the first branch of the parameter tree.");
                fw.println("# Parameter definitions in a csv file. Column headings are as below");
                fw.println(
                        "# ID, Name, Parameter Type, Description, Search String, Value Type, Value String, Selected Value Index");
                fw.println(
                        "#           {0}                                         {0, 1, 2}                 {0, .... depending on number of values}");
                fw.println("# ");
                DefaultMutableTreeNode thisleaf = ParamTree.getFirstLeaf();
                Object[] path = thisleaf.getUserObjectPath();
                for (Object obj : path) {
                    ParameterItem item = (ParameterItem) obj;
                    fw.println(item.toCSVrow());
                }
                return true;
            } catch (Exception ex) {
                logger.error("Error writing parameter table to file " + file.getAbsolutePath(), ex);
            }
        }
        return false;
    }

    /**
     * Add a list of parameter items as a branch at the given root node
     * @param root Root where the new branch is attached
     * @param list The list of parameter items
     */
    public void addParameterListAsBranch(DefaultMutableTreeNode root, List<ParameterItem> list) {
        if (list != null && list.size() > 0) {
            if (root == null) { // replace current tree
                ParamTree = new DefaultMutableTreeNode(list.get(0));
                DefaultMutableTreeNode current = ParamTree;
                for (int i = 1; i < list.size(); i++) {
                    DefaultMutableTreeNode newnode = new DefaultMutableTreeNode(list.get(i));
                    current.add(newnode);
                    current = newnode;
                }
            } else {
                DefaultMutableTreeNode current = root;
                for (ParameterItem item : list) {
                    DefaultMutableTreeNode newnode = new DefaultMutableTreeNode(item);
                    current.add(newnode);
                    current = newnode;
                }
            }
        }
    }

    public String[][] getLHSJobList(int LHSsize, Random randomsrc) {

        if (randomsrc == null)
            randomsrc = RandomSource.getRandomGenerator();

        String[][] JobList = new String[LHSsize][];

        // Get all parameters (inc. idf and weather) and their distributions
        if (ParamTree != null) {
            // Create sample for each parameter
            String[][] SampledValues = getSampleInEqualProbSegments(LHSsize, randomsrc);
            // debug
            logger.debug(Arrays.deepToString(SampledValues));
            //
            int length = SampledValues.length;
            // Shuffle the sample value vector of each parameter
            for (int i = 1; i < length; i++) {
                Collections.shuffle(Arrays.asList(SampledValues[i]), randomsrc);
            }
            // n jobs are created by taking a value from each parameter's vector 
            // sequentially
            for (int i = 0; i < LHSsize; i++) {
                JobList[i] = new String[length];
                JobList[i][0] = new Formatter().format("LHS-%06d", i).toString(); // Job id
                for (int j = 1; j < length; j++) {
                    JobList[i][j] = SampledValues[j][i];
                }
            }
            return JobList;
        }
        return null;
    }

    public String[][] getSobolJobList(int LHSsize, Random randomsrc) {

        if (randomsrc == null)
            randomsrc = RandomSource.getRandomGenerator();

        String[][] JobList = new String[LHSsize][];

        // Get all parameters (inc. idf and weather) and their distributions
        if (ParamTree != null) {
            // Create sample for each parameter
            String[][] SampledValues = getSampleInEqualProbSegments(LHSsize, randomsrc);
            int length = SampledValues.length;
            // Generate Sobol sequence
            SobolSequenceGenerator SSG = new SobolSequenceGenerator(length - 1);
            // SSG.skipTo(1000);
            // Shuffle the sample value vector of each parameter
            //            for (int i=1; i<length; i++) {
            //                Collections.shuffle(Arrays.asList(SampledValues[i]), randomsrc);
            //            }
            // n jobs are created by taking a value from each parameter's vector 
            // sequentially
            for (int i = 0; i < LHSsize; i++) {
                double[] vector = SSG.nextVector();
                JobList[i] = new String[length];
                JobList[i][0] = new Formatter().format("SOBOL-%06d", i).toString(); // Job id
                for (int j = 1; j < length; j++) {
                    JobList[i][j] = SampledValues[j][Math.round((float) vector[j - 1] * LHSsize)];
                }
            }
            return JobList;
        }
        return null;
    }

    /**
     * 
     * @param sampleSize
     * @param randomsrc
     * @return 
     */
    private String[][] getSampleInEqualProbSegments(int sampleSize, Random randomsrc) {
        DefaultMutableTreeNode thisleaf = ParamTree.getFirstLeaf();
        Object[] path = thisleaf.getUserObjectPath();
        int length = path.length + 3; // tree depth plus JobID (reserved space), IDF and Weather
        String[][] SampledValues = new String[length][];
        int n_alt;
        // First element is reserved for job id
        // Weather
        n_alt = this.parseFileListString(this.resolveWeatherDir(), this.getWeatherFile()).size();
        int[] SampledIndex = this.defaultLHSdiscreteSample(sampleSize, n_alt, randomsrc);
        SampledValues[1] = new String[sampleSize];
        for (int j = 0; j < sampleSize; j++) {
            SampledValues[1][j] = Integer.toString(SampledIndex[j]);
        }
        // IDF
        n_alt = this.parseFileListString(this.resolveIDFDir(), this.getIDFTemplate()).size();
        SampledIndex = this.defaultLHSdiscreteSample(sampleSize, n_alt, randomsrc);
        SampledValues[2] = new String[sampleSize];
        for (int j = 0; j < sampleSize; j++) {
            SampledValues[2][j] = Integer.toString(SampledIndex[j]);
        }

        // Parameters
        for (int i = 3; i < length; i++) {
            ParameterItem Param = ((ParameterItem) path[i - 3]);
            if (Param.getValuesString().startsWith("@sample")) {
                // A distribution definition
                SampledValues[i] = this.defaultLHSdistributionSample(sampleSize, Param.getValuesString(),
                        Param.getType(), randomsrc);
            } else {
                // distribution undefined; normal parameter
                n_alt = Param.getNAltValues();
                SampledIndex = this.defaultLHSdiscreteSample(sampleSize, n_alt, randomsrc);
                SampledValues[i] = new String[sampleSize];
                for (int j = 0; j < sampleSize; j++) {
                    SampledValues[i][j] = Param.getAlternativeValues()[SampledIndex[j]];
                }
            }
        }
        return SampledValues;
    }

    private int[] defaultLHSdiscreteSample(int n, int n_alt, Random randomsrc) {
        int[] index = new int[n];
        if (n_alt > 1) {
            RouletteWheel Wheel = new RouletteWheel(n_alt, randomsrc);
            for (int j = 0; j < n; j++) {
                index[j] = Wheel.spin(j * Wheel.getTotalWidth() / n, (j + 1) * Wheel.getTotalWidth() / n);
            }
        } else {
            for (int j = 0; j < n; j++)
                index[j] = 0;
        }
        return index;
    }

    private String[] defaultLHSdistributionSample(int n, String funcstr, int type, Random randomsrc) {
        // Trim off brackets
        int start = funcstr.indexOf("(") + 1;
        int end = funcstr.indexOf(")");
        funcstr = funcstr.substring(start, end).trim();

        ArrayList<String> list = new ArrayList<>();
        String[] params = funcstr.split("\\s*,\\s*");
        // For integer/double types, returns randomized N samples conforming
        // a specified distribution, currently 'gaussian'/'normal'/'n', 
        // 'uniform'/'u', 'triangular'/'tr', or 'discrete'/'d'
        // for examples: @sample(gaussian, 0, 1.5, 20), with mean, sd and N
        //           or  @sample(uniform, -10, 10, 20), with lb, ub and N
        //           of  @sample(triangular, -1.0, 0.3, 1.0, 20), with lb, mode, ub and N
        //           of  @sample(discrete, option_A, 0.3, option_B, 0.5, option_C, 0.2, 20), with lb, mode, ub and N
        String distribution = params[0].toLowerCase();
        switch (distribution) {
        case "uniform":
        case "u":
            // requires lb, ub, n
            double lb = Double.parseDouble(params[1]);
            double ub = Double.parseDouble(params[2]);
            for (int i = 0; i < n; i++) {
                if (type == ParameterItem.DOUBLE) {
                    double bin = (ub - lb) / n;
                    double v = randomsrc.nextDouble() * bin + lb + i * bin;
                    list.add(Double.toString(v));
                } else if (type == ParameterItem.INTEGER) {
                    double bin = (ub + 1. - lb) / n;
                    double v = randomsrc.nextDouble() * bin + lb + i * bin;
                    list.add(Integer.toString((int) Math.floor(v)));
                }
            }
            break;
        case "gaussian":
        case "normal":
        case "n": {
            // requires mean, sd, n
            double mean = Double.parseDouble(params[1]);
            double sd = Double.parseDouble(params[2]);
            NormalDistribution Dist = new NormalDistribution(mean, sd);
            double bin = 1.0 / n;
            for (int i = 0; i < n; i++) {
                double a = Dist.inverseCumulativeProbability((i == 0) ? bin / 10 : i * bin); // lb of each bin
                double b = Dist.inverseCumulativeProbability((i == n - 1) ? 1. - bin / n : (i + 1) * bin); // ub of each bin
                double v = randomsrc.nextDouble() * (b - a) + a;
                if (type == ParameterItem.DOUBLE) {
                    list.add(Double.toString(v));
                } else if (type == ParameterItem.INTEGER) {
                    // Warning: for integer, binomial distribution should be used.
                    // the following function is provided just for convenience
                    list.add(Long.toString(Math.round(v)));
                }
            }
            break;
        }
        case "lognormal":
        case "ln": {
            // requires mean, sd, n
            double mean = Double.parseDouble(params[1]);
            double sd = Double.parseDouble(params[2]);
            LogNormalDistribution Dist = new LogNormalDistribution(mean, sd);
            double bin = 1.0 / n;
            for (int i = 0; i < n; i++) {
                double a = Dist.inverseCumulativeProbability((i == 0) ? bin / 10 : i * bin); // lb of each bin
                double b = Dist.inverseCumulativeProbability((i == n - 1) ? 1. - bin / n : (i + 1) * bin); // ub of each bin
                double v = randomsrc.nextDouble() * (b - a) + a;
                if (type == ParameterItem.DOUBLE) {
                    list.add(Double.toString(v));
                } else if (type == ParameterItem.INTEGER) {
                    // Warning: for integer, binomial distribution should be used.
                    // the following function is provided just for convenience
                    list.add(Long.toString(Math.round(v)));
                }
            }
            break;
        }
        case "exponential":
        case "e": {
            // requires mean, sd, n
            double mean = Double.parseDouble(params[1]);
            ExponentialDistribution Dist = new ExponentialDistribution(mean);
            double bin = 1.0 / n;
            for (int i = 0; i < n; i++) {
                double a = Dist.inverseCumulativeProbability((i == 0) ? bin / 10 : i * bin); // lb of each bin
                double b = Dist.inverseCumulativeProbability((i == n - 1) ? 1. - bin / n : (i + 1) * bin); // ub of each bin
                double v = randomsrc.nextDouble() * (b - a) + a;
                if (type == ParameterItem.DOUBLE) {
                    list.add(Double.toString(v));
                } else if (type == ParameterItem.INTEGER) {
                    // Warning: for integer, binomial distribution should be used.
                    // the following function is provided just for convenience
                    list.add(Long.toString(Math.round(v)));
                }
            }
            break;
        }
        case "triangular":
        case "tr": {
            // requires a(lb), c(mode), b(ub), n
            double a = Double.parseDouble(params[1]);
            double c = Double.parseDouble(params[2]);
            double b = Double.parseDouble(params[3]);
            TriangularDistribution Dist = new TriangularDistribution(a, c, b);
            double bin = 1.0 / n;
            for (int i = 0; i < n; i++) {
                a = Dist.inverseCumulativeProbability(i * bin); // lb of each bin
                b = Dist.inverseCumulativeProbability((i + 1) * bin); // ub of each bin
                double v = randomsrc.nextDouble() * (b - a) + a;
                if (type == ParameterItem.DOUBLE) {
                    list.add(Double.toString(v));
                } else if (type == ParameterItem.INTEGER) {
                    // Warning: for integer, user defined discrete distribution should be used.
                    // the following function is provided just for convenience
                    list.add(Long.toString(Math.round(v)));
                }
            }
            break;
        }
        case "discrete":
        case "d": {
            // requires op1, prob1, op2, prob2, ..., n
            int nOptions = params.length / 2 - 1;
            String[] options = new String[nOptions];
            double[] probabilities = new double[nOptions];
            double sum = 0;
            for (int i = 0; i < nOptions; i++) {
                options[i] = params[2 * i + 1];
                try {
                    probabilities[i] = Double.parseDouble(params[2 * i + 2]);
                } catch (NumberFormatException nfe) {
                    probabilities[i] = 0.1;
                }
                sum += probabilities[i];
            }
            RouletteWheel Wheel = new RouletteWheel(probabilities, randomsrc);
            double bin = sum / n;
            for (int i = 0; i < n; i++) {
                double a = i * bin; // lb of each bin
                double b = (i + 1) * bin; // ub of each bin
                int sel = Wheel.spin(a, b);
                list.add(options[sel]);
            }
            break;
        }
        case "custom":
            break;
        }
        return list.toArray(new String[0]);
    }
}