TEST_F(AnalysisFixture, RubyContinuousVariable_UserScript) {
  RubyMeasure userScript(toPath("setGeometry.rb"),
                              FileReferenceType::OSM,
                              FileReferenceType::OSM,
                              true);
  OSArgument arg = OSArgument::makeDoubleArgument("floorToFloorHeight");
  arg.setValue(3.0);
  userScript.addArgument(arg);
  arg = OSArgument::makeIntegerArgument("numFloors");
  arg.setValue(1);
  userScript.addArgument(arg);
  EXPECT_EQ(2u,userScript.arguments().size());
  EXPECT_TRUE(userScript.isUserScript());
  EXPECT_EQ(toString(completeAndNormalize(toPath("setGeometry.rb"))),
            toString(userScript.perturbationScript().path()));

  arg = OSArgument::makeDoubleArgument("floorArea");
  RubyContinuousVariable var("Floor Area (m^2)",arg,userScript);
  var.setMinimum(500.0);
  var.setMaximum(1000.0);
  EXPECT_EQ(2u,var.measure().arguments().size());
  EXPECT_TRUE(var.measure().isUserScript());
  EXPECT_EQ(toString(completeAndNormalize(toPath("setGeometry.rb"))),
            toString(var.measure().perturbationScript().path()));
  ASSERT_TRUE(var.minimum());
  EXPECT_DOUBLE_EQ(500.0,var.minimum().get());
  ASSERT_TRUE(var.maximum());
  EXPECT_DOUBLE_EQ(1000.0,var.maximum().get());
  EXPECT_FALSE(var.increment());
  EXPECT_FALSE(var.nSteps());
  EXPECT_TRUE(var.isFeasible(500.0));
  EXPECT_FALSE(var.isFeasible(1500.0));
  ASSERT_TRUE(var.truncate(1500.0));
  EXPECT_DOUBLE_EQ(1000.0,var.truncate(1500.0).get());
  WeibullDistribution distribution(1.5,1.0);
  EXPECT_DOUBLE_EQ(1.5,distribution.alpha());
  EXPECT_DOUBLE_EQ(1.0,distribution.beta());
  EXPECT_FALSE(var.uncertaintyDescription());
  EXPECT_TRUE(var.setUncertaintyDescription(distribution));
  ASSERT_TRUE(var.uncertaintyDescription());
  ASSERT_TRUE(var.uncertaintyDescription().get().optionalCast<WeibullDistribution>());
  EXPECT_DOUBLE_EQ(1.5,var.uncertaintyDescription().get().cast<WeibullDistribution>().alpha());
  EXPECT_DOUBLE_EQ(1.0,var.uncertaintyDescription().get().cast<WeibullDistribution>().beta());
  var.resetUncertaintyDescription();
  EXPECT_FALSE(var.uncertaintyDescription());
}
openstudio::analysis::Analysis AnalysisFixture::analysis2(bool simulate) {
  // Create problem and analysis
  Problem problem("My Problem");

  BCLMeasure bclMeasure(resourcesPath() / toPath("utilities/BCL/Measures/v2/SetWindowToWallRatioByFacade"));
  RubyMeasure measure(bclMeasure);
  StringVector choices;
  choices.push_back("North");
  choices.push_back("South");
  choices.push_back("East");
  choices.push_back("West");
  OSArgument facade = OSArgument::makeChoiceArgument("facade",choices);
  OSArgument wwr = OSArgument::makeDoubleArgument("wwr");

  // RubyContinuousVariable for South Facade
  measure = measure.clone().cast<RubyMeasure>();
  OSArgument arg = facade.clone();
  arg.setValue("South");  
  measure.setArgument(arg);
  arg = wwr.clone();
  RubyContinuousVariable southWWR("South Window to Wall Ratio",arg,measure);
  problem.push(southWWR);

  // RubyContinuousVariable for North Facade
  measure = measure.clone().cast<RubyMeasure>();
  arg = facade.clone();
  arg.setValue("North");
  measure.setArgument(arg);
  arg = wwr.clone();
  RubyContinuousVariable northWWR("North Window to Wall Ratio",arg,measure);
  problem.push(northWWR);

  if (simulate) {
    problem.push(WorkItem(JobType::ModelToIdf));
    problem.push(WorkItem(JobType::EnergyPlusPreProcess));
    problem.push(WorkItem(JobType::EnergyPlus));
    problem.push(WorkItem(JobType::OpenStudioPostProcess));
  }

  Analysis analysis("My Analysis",problem,FileReferenceType::OSM);

  return analysis;
}
TEST_F(RulesetFixture, OSArgument_Domain) {
  
  OSArgument doubleArg = OSArgument::makeDoubleArgument("double", true);
  OSArgument integerArg = OSArgument::makeIntegerArgument("integer", true);
  OSArgument stringArg = OSArgument::makeStringArgument("string", true);

  EXPECT_FALSE(doubleArg.hasDomain());
  EXPECT_FALSE(integerArg.hasDomain());
  EXPECT_FALSE(stringArg.hasDomain());

  double d = 0.0;
  int i = 0;

  EXPECT_TRUE(doubleArg.setMinValue(d));
  EXPECT_TRUE(integerArg.setMinValue(i));
  EXPECT_FALSE(stringArg.setMinValue(0.0));

  ASSERT_TRUE(doubleArg.hasDomain());
  ASSERT_TRUE(integerArg.hasDomain());
  EXPECT_FALSE(stringArg.hasDomain());

  EXPECT_EQ(0.0, doubleArg.domainAsDouble()[0]);
  EXPECT_EQ(std::numeric_limits<double>::max(), doubleArg.domainAsDouble()[1]);
  EXPECT_EQ(0, integerArg.domainAsInteger()[0]);
  EXPECT_EQ(std::numeric_limits<int>::max(), integerArg.domainAsDouble()[1]);

  d = 1.0;
  i = 1;

  EXPECT_TRUE(doubleArg.setMaxValue(i));
  EXPECT_TRUE(integerArg.setMaxValue(d));
  EXPECT_FALSE(stringArg.setMaxValue(1));

  ASSERT_TRUE(doubleArg.hasDomain());
  ASSERT_TRUE(integerArg.hasDomain());
  EXPECT_FALSE(stringArg.hasDomain());

  EXPECT_EQ(0.0, doubleArg.domainAsDouble()[0]);
  EXPECT_EQ(1.0, doubleArg.domainAsDouble()[1]);
  EXPECT_EQ(0, integerArg.domainAsInteger()[0]);
  EXPECT_EQ(1, integerArg.domainAsDouble()[1]);

  // the domain is not currently used to validate these values
  EXPECT_TRUE(doubleArg.setValue(-1.0));
  EXPECT_TRUE(doubleArg.setValue(0.0));
  EXPECT_TRUE(doubleArg.setValue(0.5));
  EXPECT_TRUE(doubleArg.setValue(1.0));
  EXPECT_TRUE(doubleArg.setValue(2.0));

  EXPECT_TRUE(integerArg.setValue(-1));
  EXPECT_TRUE(integerArg.setValue(0));
  EXPECT_TRUE(integerArg.setValue(1));
  EXPECT_TRUE(integerArg.setValue(2));
}
TEST_F(RulesetFixture, OSArgument_ClearValue) {
  std::vector<std::string> choices;
  choices.push_back("On");
  choices.push_back("Off");

  OSArgument boolArgument = OSArgument::makeBoolArgument("bool");
  OSArgument doubleArgument = OSArgument::makeDoubleArgument("double");
  OSArgument integerArgument = OSArgument::makeIntegerArgument("integer");
  OSArgument stringArgument = OSArgument::makeStringArgument("string");
  OSArgument choiceArgument = OSArgument::makeChoiceArgument("choice", choices);

  EXPECT_TRUE(boolArgument.setValue(true));
  EXPECT_TRUE(doubleArgument.setValue(1.0));
  EXPECT_TRUE(doubleArgument.setValue((int)1)); // can also set double arg using int
  EXPECT_TRUE(integerArgument.setValue((int)1));
  EXPECT_TRUE(stringArgument.setValue(std::string("value")));
  EXPECT_TRUE(choiceArgument.setValue(std::string("On")));

  ASSERT_TRUE(boolArgument.hasValue());
  ASSERT_TRUE(doubleArgument.hasValue());
  ASSERT_TRUE(integerArgument.hasValue());
  ASSERT_TRUE(stringArgument.hasValue());
  ASSERT_TRUE(choiceArgument.hasValue());

  boolArgument.clearValue();
  doubleArgument.clearValue();
  integerArgument.clearValue();
  stringArgument.clearValue();
  choiceArgument.clearValue();

  EXPECT_FALSE(boolArgument.hasValue());
  EXPECT_FALSE(doubleArgument.hasValue());
  EXPECT_FALSE(integerArgument.hasValue());
  EXPECT_FALSE(stringArgument.hasValue());
  EXPECT_FALSE(choiceArgument.hasValue());  

  EXPECT_ANY_THROW(boolArgument.valueAsBool());
  EXPECT_ANY_THROW(doubleArgument.valueAsDouble());
  EXPECT_ANY_THROW(integerArgument.valueAsInteger());
  EXPECT_ANY_THROW(stringArgument.valueAsString());
  EXPECT_ANY_THROW(choiceArgument.valueAsString());
}
TEST_F(ProjectFixture, RubyMeasureRecord_BCLMeasure) {
    // Construct problem with RubyMeasure that points to BCLMeasure
    Problem problem("Problem",VariableVector(),runmanager::Workflow());
    MeasureGroup dvar("Variable",MeasureVector());
    problem.push(dvar);
    openstudio::path measuresPath = resourcesPath() / toPath("/utilities/BCL/Measures");
    openstudio::path dir = measuresPath / toPath("SetWindowToWallRatioByFacade");
    BCLMeasure measure = BCLMeasure::load(dir).get();
    RubyMeasure rpert(measure);
    dvar.push(rpert);
    OSArgument arg = OSArgument::makeDoubleArgument("wwr");
    arg.setValue(0.4);
    rpert.setArgument(arg);
    arg = OSArgument::makeIntegerArgument("typo_arg");
    arg.setDefaultValue(1);
    rpert.setArgument(arg);

    // Serialize to database
    {
        ProjectDatabase database = getCleanDatabase("RubyMeasureRecord_BCLMeasure");

        bool didStartTransaction = database.startTransaction();
        EXPECT_TRUE(didStartTransaction);

        // Problem Record
        ProblemRecord problemRecord = ProblemRecord::factoryFromProblem(problem,database);

        database.save();
        if (didStartTransaction) {
            EXPECT_TRUE(database.commitTransaction());
        }
    }

    // Re-open database, de-serialize, verify that RubyMeasure is intact.
    openstudio::path tempDir1 = measuresPath / toPath(toString(createUUID()));
    {
        ProjectDatabase database = getExistingDatabase("RubyMeasureRecord_BCLMeasure");
        ASSERT_EQ(1u,ProblemRecord::getProblemRecords(database).size());
        ASSERT_EQ(1u,MeasureGroupRecord::getMeasureGroupRecords(database).size());
        EXPECT_EQ(1u,RubyMeasureRecord::getRubyMeasureRecords(database).size());

        MeasureRecordVector dprs = MeasureGroupRecord::getMeasureGroupRecords(database)[0].measureRecords(false);
        ASSERT_EQ(1u,dprs.size());
        ASSERT_TRUE(dprs[0].optionalCast<RubyMeasureRecord>());
        RubyMeasureRecord rpr = dprs[0].cast<RubyMeasureRecord>();
        RubyMeasure rp = rpr.rubyMeasure();
        EXPECT_TRUE(rp.usesBCLMeasure());
        EXPECT_TRUE(rp.measure());
        EXPECT_EQ(dir,rp.measureDirectory());
        EXPECT_EQ(measure.uuid(),rp.measureUUID());
        EXPECT_EQ(measure.versionUUID(),rp.measureVersionUUID());
        EXPECT_ANY_THROW(rp.perturbationScript());
        EXPECT_EQ(2u,rp.arguments().size());
        EXPECT_FALSE(rp.hasIncompleteArguments());

        // Update measure and save
        BCLMeasure newVersion = measure.clone(tempDir1).get();
        newVersion.setDescription("Window to wall ratio with sill height configurable.");
        newVersion.save();
        EXPECT_NE(measure.versionUUID(),newVersion.versionUUID());
        OSArgumentVector args;
        args.push_back(OSArgument::makeDoubleArgument("wwr"));
        args.push_back(OSArgument::makeDoubleArgument("sillHeight"));

        Problem problemCopy = ProblemRecord::getProblemRecords(database)[0].problem();
        problemCopy.updateMeasure(newVersion,args,false);

        bool didStartTransaction = database.startTransaction();
        EXPECT_TRUE(didStartTransaction);

        // Problem Record
        ProblemRecord problemRecord = ProblemRecord::factoryFromProblem(problemCopy,database);

        database.save();
        if (didStartTransaction) {
            EXPECT_TRUE(database.commitTransaction());
        }
    }

    // Re-open database, check that old argument records are gone, check that de-serialized object ok
    openstudio::path tempDir2 = measuresPath / toPath(toString(createUUID()));
    {
        ProjectDatabase database = getExistingDatabase("RubyMeasureRecord_BCLMeasure");
        ASSERT_EQ(1u,ProblemRecord::getProblemRecords(database).size());
        EXPECT_EQ(1u,MeasureGroupRecord::getMeasureGroupRecords(database).size());
        EXPECT_EQ(1u,RubyMeasureRecord::getRubyMeasureRecords(database).size());
        EXPECT_EQ(1u,FileReferenceRecord::getFileReferenceRecords(database).size());
        EXPECT_EQ(2u,OSArgumentRecord::getOSArgumentRecords(database).size());

        Problem problemCopy = ProblemRecord::getProblemRecords(database)[0].problem();
        InputVariableVector vars = problemCopy.variables();
        ASSERT_FALSE(vars.empty());
        ASSERT_TRUE(vars[0].optionalCast<MeasureGroup>());
        MeasureVector dps = vars[0].cast<MeasureGroup>().measures(false);
        ASSERT_FALSE(dps.empty());
        ASSERT_TRUE(dps[0].optionalCast<RubyMeasure>());
        RubyMeasure rp = dps[0].cast<RubyMeasure>();
        EXPECT_TRUE(rp.usesBCLMeasure());
        EXPECT_TRUE(rp.measure());
        EXPECT_EQ(tempDir1,rp.measureDirectory());
        EXPECT_EQ(measure.uuid(),rp.measureUUID());
        EXPECT_NE(measure.versionUUID(),rp.measureVersionUUID());
        EXPECT_ANY_THROW(rp.perturbationScript());
        ASSERT_EQ(2u,rp.arguments().size());
        EXPECT_TRUE(rp.hasIncompleteArguments());
        EXPECT_EQ("wwr",rp.arguments()[0].name());
        ASSERT_EQ(1u,rp.incompleteArguments().size());
        EXPECT_EQ("sillHeight",rp.incompleteArguments()[0].name());

        // Set to different measure
        BCLMeasure measure2 = measure.clone(tempDir2).get();
        measure2.changeUID();
        measure2.incrementVersionId();
        measure2.save();
        measure2 = BCLMeasure::load(tempDir2).get();
        EXPECT_NE(measure.uuid(),measure2.uuid());
        EXPECT_NE(measure.versionUUID(),measure2.versionUUID());
        rp.setMeasure(measure2);
        EXPECT_TRUE(rp.isDirty());
        EXPECT_TRUE(problemCopy.isDirty());

        bool didStartTransaction = database.startTransaction();
        EXPECT_TRUE(didStartTransaction);

        // Problem Record
        ProblemRecord problemRecord = ProblemRecord::factoryFromProblem(problemCopy,database);

        database.save();
        if (didStartTransaction) {
            EXPECT_TRUE(database.commitTransaction());
        }
    }

    // Re-open database, check that old measure and all argument records are gone
    {
        ProjectDatabase database = getExistingDatabase("RubyMeasureRecord_BCLMeasure");
        ASSERT_EQ(1u,ProblemRecord::getProblemRecords(database).size());
        EXPECT_EQ(1u,MeasureGroupRecord::getMeasureGroupRecords(database).size());
        EXPECT_EQ(1u,RubyMeasureRecord::getRubyMeasureRecords(database).size());
        EXPECT_EQ(1u,FileReferenceRecord::getFileReferenceRecords(database).size());
        EXPECT_EQ(0u,OSArgumentRecord::getOSArgumentRecords(database).size());

        Problem problemCopy = ProblemRecord::getProblemRecords(database)[0].problem();
        InputVariableVector vars = problemCopy.variables();
        ASSERT_FALSE(vars.empty());
        ASSERT_TRUE(vars[0].optionalCast<MeasureGroup>());
        MeasureVector dps = vars[0].cast<MeasureGroup>().measures(false);
        ASSERT_FALSE(dps.empty());
        ASSERT_TRUE(dps[0].optionalCast<RubyMeasure>());
        RubyMeasure rp = dps[0].cast<RubyMeasure>();
        EXPECT_TRUE(rp.usesBCLMeasure());
        EXPECT_TRUE(rp.measure());
        EXPECT_EQ(tempDir2,rp.measureDirectory());
        EXPECT_NE(measure.uuid(),rp.measureUUID());
        EXPECT_NE(measure.versionUUID(),rp.measureVersionUUID());
        EXPECT_ANY_THROW(rp.perturbationScript());
        ASSERT_EQ(0u,rp.arguments().size());
        EXPECT_FALSE(rp.hasIncompleteArguments());
    }

    boost::filesystem::remove_all(tempDir1);
    boost::filesystem::remove_all(tempDir2);
}
TEST_F(AnalysisDriverFixture,SimpleProject_UpdateMeasure) {
  // create a new project
  SimpleProject project = getCleanSimpleProject("SimpleProject_UpdateMeasure");
  Problem problem = project.analysis().problem();

  // insert a measure into the project, extract and register its arguments
  openstudio::path measuresDir = resourcesPath() / toPath("/utilities/BCL/Measures");
  openstudio::path dir = measuresDir / toPath("SetWindowToWallRatioByFacade");
  BCLMeasure measure = BCLMeasure::load(dir).get();
  BCLMeasure projectMeasure = project.insertMeasure(measure);
  OSArgumentVector args = argumentGetter->getArguments(projectMeasure,
                                                       project.seedModel(),
                                                       project.seedIdf());
  project.registerArguments(projectMeasure,args);
  EXPECT_EQ(1u,project.measures().size());

  // use the measure to create a new variable/ruby measure
  MeasureGroup dv("New Measure Group",MeasureVector());
  EXPECT_TRUE(problem.push(dv));
  RubyMeasure rp(projectMeasure);
  rp.setArguments(args);
  EXPECT_TRUE(dv.push(rp));
  EXPECT_EQ(args.size(),rp.arguments().size());
  EXPECT_TRUE(rp.hasIncompleteArguments());
  BOOST_FOREACH(const OSArgument& arg,args) {
    if (arg.name() == "wwr") {
      OSArgument temp = arg.clone();
      temp.setValue(0.6);
      rp.setArgument(temp);
    }
    if (arg.name() == "sillHeight") {
      OSArgument temp = arg.clone();
      temp.setValue(1.0);
      rp.setArgument(temp);
    }
    if (arg.name() == "facade") {
      OSArgument temp = arg.clone();
      temp.setValue("South");
      rp.setArgument(temp);
    }
  }
  EXPECT_FALSE(rp.hasIncompleteArguments());

  openstudio::path tempDir = measuresDir / toPath(toString(createUUID()));
  {
    // create fake new version of the measure
    BCLMeasure newVersion = measure.clone(tempDir).get();
    newVersion.incrementVersionId();
    newVersion.save();
    OSArgumentVector newArgs = args;
    newArgs.push_back(OSArgument::makeDoubleArgument("frame_width"));

    // update the measure
    project.updateMeasure(newVersion,newArgs);

    // verify the final state of SimpleProject and RubyMeasure
    EXPECT_EQ(1u,project.measures().size());
    BCLMeasure retrievedMeasure = project.getMeasureByUUID(measure.uuid()).get();
    EXPECT_NE(measure.versionUUID(),retrievedMeasure.versionUUID());
    EXPECT_EQ(newVersion.versionUUID(),retrievedMeasure.versionUUID());
    ASSERT_TRUE(project.hasStoredArguments(retrievedMeasure));
    OSArgumentVector retrievedArgs = project.getStoredArguments(retrievedMeasure);
    EXPECT_EQ(args.size() + 1u,retrievedArgs.size());

    EXPECT_EQ(retrievedArgs.size(),rp.arguments().size());
    EXPECT_TRUE(rp.hasIncompleteArguments());
  }
  boost::filesystem::remove_all(tempDir);
}
openstudio::analysis::Analysis AnalysisFixture::analysis1(AnalysisState state) {
  // Create problem and analysis
  Problem problem("My Problem");

  BCLMeasure bclMeasure(resourcesPath() / toPath("utilities/BCL/Measures/SetWindowToWallRatioByFacade"));
  RubyMeasure measure(bclMeasure);

  // Measure Group
  StringVector choices;
  choices.push_back("North");
  choices.push_back("South");
  choices.push_back("East");
  choices.push_back("West");
  OSArgument facade = OSArgument::makeChoiceArgument("facade",choices);
  OSArgument arg = facade.clone();
  arg.setValue("South");
  measure.setArgument(arg);
  OSArgument wwr = OSArgument::makeDoubleArgument("wwr");
  MeasureVector measures(1u,NullMeasure());
  measures.push_back(measure.clone().cast<Measure>());
  arg = wwr.clone();
  arg.setValue(0.1);
  measures.back().cast<RubyMeasure>().setArgument(arg);
  measures.push_back(measure.clone().cast<Measure>());
  arg = wwr.clone();
  arg.setValue(0.2);
  measures.back().cast<RubyMeasure>().setArgument(arg);
  measures.push_back(measure.clone().cast<Measure>());
  arg = wwr.clone();
  arg.setValue(0.3);
  measures.back().cast<RubyMeasure>().setArgument(arg);
  problem.push(MeasureGroup("South Windows",measures));

  // Continuous Variables Attached to Arguments
  arg = facade.clone();
  arg.setValue("North");
  measure.setArgument(arg);
  arg = wwr.clone();
  RubyContinuousVariable wwrCV("Window to Wall Ratio",arg,measure);
  wwrCV.setMinimum(0.0);
  wwrCV.setMaximum(1.0);
  TriangularDistribution td(0.2,0.0,0.5);
  wwrCV.setUncertaintyDescription(td);
  problem.push(wwrCV);
  OSArgument offset = OSArgument::makeDoubleArgument("offset");
  RubyContinuousVariable offsetCV("Offset",offset,measure);
  offsetCV.setMinimum(0.0);
  offsetCV.setMaximum(1.5);
  NormalDistribution nd(0.9,0.05);
  offsetCV.setUncertaintyDescription(nd);
  problem.push(offsetCV);

  // Simulation
  problem.push(WorkItem(JobType::ModelToIdf));
  problem.push(WorkItem(JobType::EnergyPlusPreProcess));
  problem.push(WorkItem(JobType::EnergyPlus));
  problem.push(WorkItem(JobType::OpenStudioPostProcess));

  // Responses
  LinearFunction response1("Energy Use Intensity",
                           VariableVector(1u,OutputAttributeVariable("EUI","site.eui")));
  problem.pushResponse(response1);
  VariableVector vars;
  vars.push_back(OutputAttributeVariable("Heating Energy","heating.energy.gas"));
  vars.push_back(OutputAttributeVariable("Cooling Energy","cooling.energy.elec"));
  DoubleVector coeffs;
  coeffs.push_back(1.0); // approx. source factor
  coeffs.push_back(2.5); // approx. source factor
  LinearFunction response2("Approximate Source Energy",vars,coeffs);
  problem.pushResponse(response2);
  LinearFunction response3("North WWR",VariableVector(1u,wwrCV)); // input variable as output
  problem.pushResponse(response3);

  Analysis analysis("My Analysis",problem,FileReferenceType::OSM);

  if (state == PreRun) {
    // Add three DataPoints
    std::vector<QVariant> values;
    values.push_back(0);
    values.push_back(0.2);
    values.push_back(0.9);
    OptionalDataPoint dataPoint = problem.createDataPoint(values);
    analysis.addDataPoint(*dataPoint);
    values[0] = 1; values[1] = 0.21851789; values[2] = 1.1681938;
    dataPoint = problem.createDataPoint(values);
    analysis.addDataPoint(*dataPoint);
    values[0] = 2; values[1] = 0.0; values[2] = 0.581563892;
    dataPoint = problem.createDataPoint(values);
    analysis.addDataPoint(*dataPoint);
  }
  else {
    // state == PostRun
    // Add one complete DataPoint
    std::vector<QVariant> values;
    values.push_back(1);
    values.push_back(0.3);
    values.push_back(0.9);
    DoubleVector responseValues;
    responseValues.push_back(58.281967);
    responseValues.push_back(718952.281);
    responseValues.push_back(0.3);
    TagVector tags;
    tags.push_back(Tag("custom"));
    tags.push_back(Tag("faked"));
    // attributes
    AttributeVector attributes;
    attributes.push_back(Attribute("electricity.Cooling",281.281567,"kWh"));
    attributes.push_back(Attribute("electricity.Lighting",19206.291876,"kWh"));
    attributes.push_back(Attribute("electricity.Equipment",5112.125718,"kWh"));
    attributes = AttributeVector(1u,Attribute("enduses.electric",attributes));
    attributes.push_back(Attribute("eui",createQuantity(128.21689,"kBtu/ft^2").get()));
    // complete job
    // 1. get vector of work items
    std::vector<WorkItem> workItems;
    // 0
    workItems.push_back(problem.variables()[0].cast<MeasureGroup>().createWorkItem(QVariant(1),
                                                                                   toPath(rubyOpenStudioDir())));
    RubyContinuousVariable rcv = problem.variables()[1].cast<RubyContinuousVariable>();
    RubyMeasure rm = rcv.measure();
    OSArgument arg = rcv.argument().clone();
    arg.setValue(values[1].toDouble());
    rm.setArgument(arg);
    rcv = problem.variables()[2].cast<RubyContinuousVariable>();
    arg = rcv.argument().clone();
    arg.setValue(values[2].toDouble());
    rm.setArgument(arg);
    // 1
    workItems.push_back(rm.createWorkItem(toPath(rubyOpenStudioDir())));
    // 2
    workItems.push_back(WorkItem(JobType::ModelToIdf));
    // 3
    workItems.push_back(WorkItem(JobType::EnergyPlusPreProcess));
    // 4
    workItems.push_back(WorkItem(JobType::EnergyPlus));
    // 5
    workItems.push_back(WorkItem(JobType::OpenStudioPostProcess));
    // 2. step through work items and create jobs with results
    WorkItem wi = workItems[5];
    std::vector<FileInfo> inFiles = wi.files.files();
    inFiles.push_back(FileInfo("eplusout.sql",
                               DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,33,32)),
                               "",
                               toPath("myProject/fakeDataPoint/75-OpenStudioPostProcess-0/eplusout.sql")));                             
    Files inFilesObject(inFiles);
    std::vector<std::pair<ErrorType, std::string> > errors;
    errors.push_back(std::make_pair(ErrorType::Info,"Post-process completed successfully."));
    JobErrors errorsObject(OSResultValue::Success,errors);
    std::vector<FileInfo> outFiles;
    outFiles.push_back(FileInfo("report.xml",
                                DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,34,21)),
                                "",
                                toPath("myProject/fakeDataPoint/75-OpenStudioPostProcess-0/report.xml")));
    Files outFilesObject(outFiles);
    Job job = JobFactory::createJob(
          wi.type,
          wi.tools,
          wi.params,
          inFilesObject,
          std::vector<openstudio::URLSearchPath>(),
          false,
          createUUID(),
          JobState(
            DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,34,21)),
            errorsObject,
            outFilesObject,
            AdvancedStatus(),
            openstudio::path())

          ); // OpenStudioPostProcess

    Job jobLast = job;
    wi = workItems[4];
    inFiles = wi.files.files();
    inFiles.push_back(FileInfo("in.idf",
                                DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,23,05)),
                                "",
                                toPath("myProject/fakeDataPoint/74-EnergyPlus-0/in.idf")));
    inFilesObject = Files(inFiles);
    errors.clear();
    errors.push_back(std::make_pair(ErrorType::Warning,"ENERGYPLUS WARNING: ..."));
    errors.push_back(std::make_pair(ErrorType::Warning,"ENERGYPLUS WARNING: ..."));
    errors.push_back(std::make_pair(ErrorType::Warning,"ENERGYPLUS WARNING: ..."));
    errorsObject = JobErrors(OSResultValue::Success,errors);
    outFiles.clear();
    outFiles.push_back(FileInfo("eplusout.sql",
                                DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,33,32)),
                                "",
                                toPath("myProject/fakeDataPoint/74-EnergyPlus-0/eplusout.sql")));
    outFiles.push_back(FileInfo("eplusout.err",
                                DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,33,34)),
                                "",
                                toPath("myProject/fakeDataPoint/74-EnergyPlus-0/eplusout.err")));
    outFilesObject = Files(outFiles);
    job = JobFactory::createJob(
          wi.type,
          wi.tools,
          wi.params,
          inFilesObject,
          std::vector<openstudio::URLSearchPath>(),
          false,
          createUUID(),
          JobState(
            DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,33,42)),
            errorsObject,
            outFilesObject,
            AdvancedStatus(),
            openstudio::path())
          ); // EnergyPlus
    job.addChild(jobLast);

    jobLast = job;
    wi = workItems[3];
    inFiles = wi.files.files();
    inFiles.push_back(FileInfo("in.idf",
                                DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,22,30)),
                                "",
                                toPath("myProject/fakeDataPoint/73-EnergyPlusPreProcess-0/in.idf")));
    inFilesObject = Files(inFiles);
    errors.clear();
    errorsObject = JobErrors(OSResultValue::Success,errors);
    outFiles.clear();
    outFiles.push_back(FileInfo("out.idf",
                                DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,23,05)),
                                "",
                                toPath("myProject/fakeDataPoint/73-EnergyPlusPreProcess-0/out.idf")));
    outFilesObject = Files(outFiles);
    job = JobFactory::createJob(
          wi.type,
          wi.tools,
          wi.params,
          inFilesObject,
          std::vector<openstudio::URLSearchPath>(),
          false,
          createUUID(),
          JobState(
            DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,23,12)),
            errorsObject,
            outFilesObject,
            AdvancedStatus(),
            openstudio::path())
          ); // EnergyPlusPreProcess
    job.addChild(jobLast);

    jobLast = job;
    wi = workItems[2];
    inFiles = wi.files.files();
    inFiles.push_back(FileInfo("in.osm",
                                DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,22,01)),
                                "",
                                toPath("myProject/fakeDataPoint/72-ModelToIdf-0/in.osm")));
    inFilesObject = Files(inFiles);
    errors.clear();
    errors.push_back(std::make_pair(ErrorType::Info,"Did not find ScheduleTypeLimits for Schedule ..."));
    errors.push_back(std::make_pair(ErrorType::Warning,"Unexpectedly did not find a child object of a certain type, replaced with a default one."));
    errorsObject = JobErrors(OSResultValue::Success,errors);
    outFiles.clear();
    outFiles.push_back(FileInfo("out.idf",
                                DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,22,30)),
                                "",
                                toPath("myProject/fakeDataPoint/72-ModelToIdf-0/out.idf")));
    outFilesObject = Files(outFiles);
    job = JobFactory::createJob(
          wi.type,
          wi.tools,
          wi.params,
          inFilesObject,
          std::vector<openstudio::URLSearchPath>(),
          false,
          createUUID(),
          JobState(
            DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,22,32)),
            errorsObject,
            outFilesObject,
            AdvancedStatus(),
            openstudio::path())
          ); // ModelToIdf
    job.addChild(jobLast);

    jobLast = job;
    wi = workItems[1];
    errors.clear();
    errors.push_back(std::make_pair(ErrorType::InitialCondition,"Started with a window to wall ratio of ..."));
    errors.push_back(std::make_pair(ErrorType::FinalCondition,"Set the window to wall ratio ..."));
    errorsObject = JobErrors(OSResultValue::Success,errors);
    outFiles.clear();
    outFilesObject = Files(outFiles);
    wi.params.append("outdir","myProject/fakeDataPoint/");
    job = JobFactory::createJob(
          wi.type,
          wi.tools,
          wi.params,
          wi.files,
          std::vector<openstudio::URLSearchPath>(),
          false,
          createUUID(),
          JobState(
            DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,21,52)),
            errorsObject,
            outFilesObject,
            AdvancedStatus(),
            openstudio::path())
          ); // Variables 2 & 3
    job.addChild(jobLast);

    jobLast = job;
    wi = workItems[0];
    errors.clear();
    errors.push_back(std::make_pair(ErrorType::InitialCondition,"Started with a window to wall ratio of ..."));
    errors.push_back(std::make_pair(ErrorType::FinalCondition,"Set the window to wall ratio ..."));
    errorsObject = JobErrors(OSResultValue::Success,errors);
    outFiles.clear();
    outFilesObject = Files(outFiles);
    // add outdir job param
    JobParams params = wi.params;
    params.append("outdir","myProject/fakeDataPoint");
    job = JobFactory::createJob(
          wi.type,
          wi.tools,
          params,
          wi.files,
          std::vector<openstudio::URLSearchPath>(),
          false,
          createUUID(),
          JobState(
            DateTime(Date(MonthOfYear::Mar,21,2018),Time(0,8,21,10)),
            errorsObject,
            outFilesObject,
            AdvancedStatus(),
            openstudio::path())
          ); // Variable 1
    job.addChild(jobLast);

    DataPoint dataPoint(createUUID(),
                        createUUID(),
                        "fakeDataPoint",
                        "Fake Data Point",
                        "Demonstrating json serialization of complete DataPoint.",
                        problem,
                        true,
                        false,
                        true,
                        DataPointRunType::Local,
                        values,
                        responseValues,
                        toPath("myProject/fakeDataPoint/"),
                        FileReference(toPath("myProject/fakeDataPoint/71-Ruby-0/out.osm")),
                        FileReference(toPath("myProject/fakeDataPoint/72-ModelToIdf-0/out.idf")),
                        FileReference(toPath("myProject/fakeDataPoint/74-EnergyPlus-0/eplusout.sql")),
                        FileReferenceVector(1u,FileReference(toPath("myProject/fakeDataPoint/75-OpenStudioPostProcess-0/report.xml"))),
                        job,
                        std::vector<openstudio::path>(),
                        tags,
                        attributes);
    EXPECT_TRUE(analysis.addDataPoint(dataPoint));
  }

  return analysis;
}
TEST_F(AnalysisFixture, Problem_UpdateMeasure_MeasureGroups) {
  // open up example measure
  openstudio::path measuresPath = resourcesPath() / toPath("/utilities/BCL/Measures/v2");
  openstudio::path dir = measuresPath / toPath("SetWindowToWallRatioByFacade");
  ASSERT_TRUE(BCLMeasure::load(dir));
  BCLMeasure measure1 = BCLMeasure::load(dir).get();
  openstudio::path tempDir1 = measuresPath / toPath(toString(createUUID()));
  openstudio::path tempDir2 = measuresPath / toPath(toString(createUUID()));
  {
    // create multiple BCLMeasures
    BCLMeasure measure1_1 = measure1.clone(tempDir1).get();
    measure1_1.setDescription("Window to wall ratio by wwr and offset.");
    measure1_1.save();
    EXPECT_TRUE(measure1_1.uuid() == measure1.uuid());
    EXPECT_FALSE(measure1_1.versionUUID() == measure1.versionUUID());
    BCLMeasure measure2 = measure1.clone(tempDir2).get();
    measure2.changeUID();
    measure2.incrementVersionId();
    measure2.save();
    EXPECT_FALSE(measure2.uuid() == measure1.uuid());
    EXPECT_FALSE(measure2.versionUUID() == measure1.versionUUID());

    // create args for those measures
    OSArgumentVector args1, args1_1, args2;
    args1.push_back(OSArgument::makeDoubleArgument("wwr"));
    args1.push_back(OSArgument::makeDoubleArgument("sillHeight"));
    args1_1.push_back(OSArgument::makeDoubleArgument("wwr"));
    args1_1.push_back(OSArgument::makeDoubleArgument("offset"));
    args1_1.push_back(OSArgument::makeDoubleArgument("vt"));
    args2.push_back(OSArgument::makeIntegerArgument("numPeople"));

    // create a problem that uses multiple BCLMeasures
    Problem problem("Problem",VariableVector(),runmanager::Workflow());
    MeasureGroup dv("South WWR",MeasureVector(1u,NullMeasure()));
    problem.push(dv);
    RubyMeasure rp(measure1);
    rp.setArguments(args1);
    dv.push(rp);
    dv.push(rp.clone().cast<RubyMeasure>());
    ASSERT_EQ(3u,dv.numMeasures(false));
    rp = dv.measures(false)[2].cast<RubyMeasure>();
    EXPECT_EQ(2u,rp.arguments().size());
    EXPECT_TRUE(rp.hasIncompleteArguments());
    dv = MeasureGroup("Occupancy",MeasureVector(1u,NullMeasure()));
    problem.push(dv);
    rp = RubyMeasure(measure2);
    rp.setArguments(args2);
    dv.push(rp);
    OSArgument arg = args2[0].clone();
    arg.setValue(100);
    rp.setArgument(arg);
    EXPECT_EQ(1u,rp.arguments().size());
    EXPECT_FALSE(rp.hasIncompleteArguments());
    dv = MeasureGroup("North WWR",MeasureVector(1u,NullMeasure()));
    problem.push(dv);
    rp = RubyMeasure(measure1);
    rp.setArguments(args1);
    arg = args1[0].clone();
    arg.setValue(0.32);
    rp.setArgument(arg);
    arg = args1[1].clone();
    arg.setValue(1.0);
    rp.setArgument(arg);
    EXPECT_EQ(2u,rp.arguments().size());
    EXPECT_FALSE(rp.hasIncompleteArguments());
    dv.push(rp);
    EXPECT_EQ(2u,dv.numMeasures(false));

    // call update
    problem.clearDirtyFlag();
    problem.updateMeasure(measure1_1,args1_1,false);

    // check state
    VariableVector vars = castVector<Variable>(problem.variables());
    ASSERT_EQ(3u,vars.size());
    EXPECT_TRUE(vars[0].isDirty());
    EXPECT_FALSE(vars[1].isDirty());
    EXPECT_TRUE(vars[2].isDirty());

    dv = vars[0].cast<MeasureGroup>();
    ASSERT_EQ(3u,dv.numMeasures(false));
    MeasureVector ps = dv.measures(false);
    EXPECT_FALSE(ps[0].isDirty());
    EXPECT_TRUE(ps[1].isDirty());
    rp = ps[1].cast<RubyMeasure>();
    EXPECT_EQ(3u,rp.arguments().size());
    EXPECT_EQ(3u,rp.incompleteArguments().size());
    EXPECT_TRUE(ps[2].isDirty());
    rp = ps[2].cast<RubyMeasure>();
    EXPECT_EQ(3u,rp.arguments().size());
    EXPECT_EQ(3u,rp.incompleteArguments().size());

    dv = vars[2].cast<MeasureGroup>();
    ASSERT_EQ(2u,dv.numMeasures(false));
    ps = dv.measures(false);
    EXPECT_FALSE(ps[0].isDirty());
    EXPECT_TRUE(ps[1].isDirty());
    rp = ps[1].cast<RubyMeasure>();
    EXPECT_EQ(3u,rp.arguments().size());
    EXPECT_EQ(2u,rp.incompleteArguments().size());
  }
  boost::filesystem::remove_all(tempDir1);
  boost::filesystem::remove_all(tempDir2);
}
TEST_F(RulesetFixture, OSArgument_Clone) {

  std::vector<std::string> choices;
  choices.push_back("On");
  choices.push_back("Off");

  OSArgument boolArgument = OSArgument::makeBoolArgument("bool");
  OSArgument doubleArgument = OSArgument::makeDoubleArgument("double");
  OSArgument integerArgument = OSArgument::makeIntegerArgument("integer");
  OSArgument stringArgument = OSArgument::makeStringArgument("string");
  OSArgument choiceArgument = OSArgument::makeChoiceArgument("choice", choices);

  EXPECT_FALSE(boolArgument.hasValue());
  EXPECT_FALSE(doubleArgument.hasValue());
  EXPECT_FALSE(integerArgument.hasValue());
  EXPECT_FALSE(stringArgument.hasValue());
  EXPECT_FALSE(choiceArgument.hasValue());

  EXPECT_TRUE(boolArgument.setValue(true));
  EXPECT_TRUE(doubleArgument.setValue(1.0));
  EXPECT_TRUE(doubleArgument.setValue((int)1)); // can also set double arg using int
  EXPECT_TRUE(integerArgument.setValue((int)1));
  EXPECT_TRUE(stringArgument.setValue(std::string("value")));
  EXPECT_TRUE(choiceArgument.setValue(std::string("On")));

  ASSERT_TRUE(boolArgument.hasValue());
  ASSERT_TRUE(doubleArgument.hasValue());
  ASSERT_TRUE(integerArgument.hasValue());
  ASSERT_TRUE(stringArgument.hasValue());
  ASSERT_TRUE(choiceArgument.hasValue());

  EXPECT_EQ(true, boolArgument.valueAsBool());
  EXPECT_EQ(1.0, doubleArgument.valueAsDouble());
  EXPECT_EQ(1, integerArgument.valueAsInteger());
  EXPECT_EQ("value", stringArgument.valueAsString());
  EXPECT_EQ("On", choiceArgument.valueAsString());

  OSArgument boolArgument2 = boolArgument.clone();
  OSArgument doubleArgument2 = doubleArgument.clone();
  OSArgument integerArgument2 = integerArgument.clone();
  OSArgument stringArgument2 = stringArgument.clone();
  OSArgument choiceArgument2 = choiceArgument.clone();

  ASSERT_TRUE(boolArgument2.hasValue());
  ASSERT_TRUE(doubleArgument2.hasValue());
  ASSERT_TRUE(integerArgument2.hasValue());
  ASSERT_TRUE(stringArgument2.hasValue());
  ASSERT_TRUE(choiceArgument2.hasValue());

  EXPECT_EQ(true, boolArgument2.valueAsBool());
  EXPECT_EQ(1.0, doubleArgument2.valueAsDouble());
  EXPECT_EQ(1, integerArgument2.valueAsInteger());
  EXPECT_EQ("value", stringArgument2.valueAsString());
  EXPECT_EQ("On", choiceArgument2.valueAsString());

  std::vector<OSArgument> argumentVector;
  argumentVector.push_back(boolArgument);
  argumentVector.push_back(doubleArgument);
  argumentVector.push_back(integerArgument);
  argumentVector.push_back(stringArgument);
  argumentVector.push_back(choiceArgument);
  std::map<std::string,OSArgument> argumentMap = convertOSArgumentVectorToMap(argumentVector);

  ASSERT_FALSE(argumentMap.find("bool") == argumentMap.end());
  ASSERT_FALSE(argumentMap.find("double") == argumentMap.end());
  ASSERT_FALSE(argumentMap.find("integer") == argumentMap.end());
  ASSERT_FALSE(argumentMap.find("string") == argumentMap.end());
  ASSERT_FALSE(argumentMap.find("choice") == argumentMap.end());

  ASSERT_TRUE(argumentMap.find("bool")->second.hasValue());
  ASSERT_TRUE(argumentMap.find("double")->second.hasValue());
  ASSERT_TRUE(argumentMap.find("integer")->second.hasValue());
  ASSERT_TRUE(argumentMap.find("string")->second.hasValue());
  ASSERT_TRUE(argumentMap.find("choice")->second.hasValue());

  EXPECT_EQ(true, argumentMap.find("bool")->second.valueAsBool());
  EXPECT_EQ(1.0, argumentMap.find("double")->second.valueAsDouble());
  EXPECT_EQ(1, argumentMap.find("integer")->second.valueAsInteger());
  EXPECT_EQ("value", argumentMap.find("string")->second.valueAsString());
  EXPECT_EQ("On", argumentMap.find("choice")->second.valueAsString());

}
TEST_F(AnalysisFixture, RubyPerturbation_BCLMeasure) {
  // construct
  openstudio::path measuresPath = resourcesPath() / toPath("/utilities/BCL/Measures");
  openstudio::path dir = measuresPath / toPath("SetWindowToWallRatioByFacade");
  BCLMeasure measure = BCLMeasure::load(dir).get();
  RubyPerturbation perturbation(measure);
  EXPECT_TRUE(perturbation.usesBCLMeasure());
  ASSERT_TRUE(perturbation.measure());
  EXPECT_TRUE(perturbation.measure().get() == measure);
  EXPECT_EQ(dir,perturbation.measureDirectory());
  EXPECT_TRUE(measure.uuid() == perturbation.measureUUID());
  EXPECT_TRUE(measure.versionUUID() == perturbation.measureVersionUUID());
  EXPECT_ANY_THROW(perturbation.perturbationScript());
  // isUserScript value is ignored in this case
  EXPECT_TRUE(perturbation.arguments().empty());
  EXPECT_FALSE(perturbation.hasIncompleteArguments());

  // add arguments to fill in
  OSArgumentVector args;
  args.push_back(OSArgument::makeDoubleArgument("wwr"));
  args.push_back(OSArgument::makeDoubleArgument("sillHeight"));
  perturbation.setArguments(args);

  // verify that arguments are incomplete
  EXPECT_EQ(2u,perturbation.incompleteArguments().size());

  // fill in argument values
  OSArgument arg = OSArgument::makeDoubleArgument("wwr");
  EXPECT_TRUE(arg.setValue(0.8));
  perturbation.setArgument(arg);
  EXPECT_EQ(2u,perturbation.arguments().size());
  EXPECT_EQ(1u,perturbation.incompleteArguments().size());
  EXPECT_TRUE(perturbation.hasIncompleteArguments());
  arg = OSArgument::makeDoubleArgument("sillHeight");
  arg.setValue("0.1");
  perturbation.setArgument(arg);
  EXPECT_EQ(2u,perturbation.arguments().size());
  EXPECT_EQ(0u,perturbation.incompleteArguments().size());
  EXPECT_FALSE(perturbation.hasIncompleteArguments());

  // update measure
  openstudio::path tempDir = measuresPath / toPath(toString(createUUID()));
  {
    BCLMeasure newVersion = measure.clone(tempDir).get();
    newVersion.setDescription("Window to wall ratio by wwr and offset.");
    newVersion.save();
    EXPECT_TRUE(newVersion.uuid() == measure.uuid());
    EXPECT_FALSE(newVersion.versionUUID() == measure.versionUUID());
    args.pop_back();
    args.push_back(OSArgument::makeDoubleArgument("offset"));
    perturbation.updateMeasure(newVersion,args);
    EXPECT_TRUE(perturbation.usesBCLMeasure());
    ASSERT_TRUE(perturbation.measure());
    EXPECT_TRUE(perturbation.measure().get() == newVersion);
    EXPECT_EQ(tempDir,perturbation.measureDirectory());
    EXPECT_TRUE(newVersion.uuid() == perturbation.measureUUID());
    EXPECT_TRUE(newVersion.versionUUID() == perturbation.measureVersionUUID());
    EXPECT_ANY_THROW(perturbation.perturbationScript());

    // verify that arguments updated correctly, values retained
    EXPECT_EQ(2u,perturbation.arguments().size());
    ASSERT_EQ(1u,perturbation.incompleteArguments().size());
    EXPECT_TRUE(perturbation.hasIncompleteArguments());
    EXPECT_EQ("offset",perturbation.incompleteArguments()[0].name());
    EXPECT_EQ("wwr",perturbation.arguments()[0].name());
    EXPECT_EQ(0.8,perturbation.arguments()[0].valueAsDouble());

    // set measure and verify that arguments cleared
    perturbation.setMeasure(newVersion); // always goes through setting motions, even if same
    EXPECT_TRUE(perturbation.usesBCLMeasure());
    ASSERT_TRUE(perturbation.measure());
    EXPECT_TRUE(perturbation.measure().get() == newVersion);
    EXPECT_EQ(tempDir,perturbation.measureDirectory());
    EXPECT_TRUE(newVersion.uuid() == perturbation.measureUUID());
    EXPECT_TRUE(newVersion.versionUUID() == perturbation.measureVersionUUID());
    EXPECT_ANY_THROW(perturbation.perturbationScript());
    // isUserScript value is ignored in this case
    EXPECT_TRUE(perturbation.arguments().empty());
    EXPECT_FALSE(perturbation.hasIncompleteArguments());
  }
  boost::filesystem::remove_all(tempDir);
}
TEST_F(RulesetFixture, UserScript_TestModelUserScript2) {
  TestModelUserScript2 script;
  EXPECT_EQ("TestModelUserScript2", script.name());

  Model model;

  // serialize ossrs
  openstudio::path fileDir = toPath("./OSResultOSSRs");
  boost::filesystem::create_directory(fileDir);

  // call with no arguments
  TestOSRunner runner;
  std::map<std::string, OSArgument> user_arguments;
  bool ok = script.run(model,runner,user_arguments);
  EXPECT_FALSE(ok);
  OSResult result = runner.result();
  EXPECT_TRUE(result.value() == OSResultValue::Fail);
  EXPECT_EQ(2u,result.errors().size()); // missing required and defaulted arguments
  EXPECT_EQ(0u,result.warnings().size());
  EXPECT_EQ(0u,result.info().size());
  EXPECT_FALSE(result.initialCondition());
  EXPECT_FALSE(result.finalCondition());
  EXPECT_TRUE(result.attributes().empty());
  result.save(fileDir / toPath("TestModelUserScript2_1.ossr"),true);

  // call with required argument, but no lights definitions in model
  LightsDefinition lightsDef(model);
  OSArgumentVector definitions = script.arguments(model);
  user_arguments = runner.getUserInput(definitions);
  OSArgument arg = definitions[0];
  arg.setValue(toString(lightsDef.handle()));
  user_arguments["lights_definition"] = arg;
  lightsDef.remove();
  EXPECT_EQ(0u,model.numObjects());
  ok = script.run(model,runner,user_arguments);
  EXPECT_FALSE(ok);
  result = runner.result();
  EXPECT_TRUE(result.value() == OSResultValue::Fail);
  EXPECT_EQ(1u,result.errors().size()); // object not in model
  EXPECT_EQ(0u,result.warnings().size());
  EXPECT_EQ(0u,result.info().size());
  EXPECT_FALSE(result.initialCondition());
  EXPECT_FALSE(result.finalCondition());
  EXPECT_EQ(2u,result.attributes().size()); // registers argument values
  result.save(fileDir / toPath("TestModelUserScript2_2.ossr"),true);
  // save attributes json for inspection
  saveJSON(result.attributes(),fileDir / toPath("TestModelUserScript2_2.json"),true);

  // call properly using default multiplier, but lights definition not Watts/Area
  lightsDef = LightsDefinition(model);
  lightsDef.setLightingLevel(700.0);
  definitions = script.arguments(model);
  user_arguments = runner.getUserInput(definitions);
  arg = definitions[0];
  arg.setValue(toString(lightsDef.handle()));
  user_arguments["lights_definition"] = arg;
  ok = script.run(model,runner,user_arguments);
  EXPECT_TRUE(ok);
  result = runner.result();
  EXPECT_TRUE(result.value() == OSResultValue::NA);
  EXPECT_EQ(0u,result.errors().size());
  EXPECT_EQ(0u,result.warnings().size());
  EXPECT_EQ(1u,result.info().size()); // Measure not applicable as called
  EXPECT_FALSE(result.initialCondition());
  EXPECT_FALSE(result.finalCondition());
  EXPECT_EQ(3u,result.attributes().size()); // Registers lights definition name, then fails
  result.save(fileDir / toPath("TestModelUserScript2_3.ossr"),true);
  // save attributes json for inspection
  saveJSON(result.attributes(),fileDir / toPath("TestModelUserScript2_3.json"),true);

  // call properly using default multiplier
  lightsDef.setWattsperSpaceFloorArea(10.0);
  ok = script.run(model,runner,user_arguments);
  EXPECT_TRUE(ok);
  result = runner.result();
  EXPECT_TRUE(result.value() == OSResultValue::Success);
  EXPECT_EQ(0u,result.errors().size());
  EXPECT_EQ(0u,result.warnings().size());
  EXPECT_EQ(0u,result.info().size());
  EXPECT_TRUE(result.initialCondition()); // describes original state
  EXPECT_TRUE(result.finalCondition());   // describes changes
  EXPECT_EQ(8u,result.attributes().size());
  result.save(fileDir / toPath("TestModelUserScript2_4.ossr"),true);
  EXPECT_DOUBLE_EQ(8.0,lightsDef.wattsperSpaceFloorArea().get());
  // save attributes json for inspection
  saveJSON(result.attributes(),fileDir / toPath("TestModelUserScript2_4.json"),true);

  // call properly using different multiplier
  arg = definitions[1];
  arg.setValue(0.5);
  user_arguments["multiplier"] = arg;
  ok = script.run(model,runner,user_arguments);
  EXPECT_TRUE(ok);
  result = runner.result();
  EXPECT_TRUE(result.value() == OSResultValue::Success);
  EXPECT_EQ(0u,result.errors().size());
  EXPECT_EQ(0u,result.warnings().size());
  EXPECT_EQ(0u,result.info().size());
  EXPECT_TRUE(result.initialCondition()); // describes original state
  EXPECT_TRUE(result.finalCondition());   // describes changes
  EXPECT_EQ(8u,result.attributes().size());
  result.save(fileDir / toPath("TestModelUserScript2_5.ossr"),true);
  EXPECT_DOUBLE_EQ(4.0,lightsDef.wattsperSpaceFloorArea().get());
  // save attributes json for inspection
  saveJSON(result.attributes(),fileDir / toPath("TestModelUserScript2_5.json"),true);

  // check that can load ossrs
  OptionalOSResult temp = OSResult::load(fileDir / toPath("TestModelUserScript2_1.ossr"));
  ASSERT_TRUE(temp);
  result = temp.get();
  EXPECT_TRUE(result.value() == OSResultValue::Fail);
  EXPECT_EQ(2u,result.errors().size()); // missing required argument
  EXPECT_EQ(0u,result.warnings().size());
  EXPECT_EQ(0u,result.info().size());
  EXPECT_FALSE(result.initialCondition());
  EXPECT_FALSE(result.finalCondition());

  temp = OSResult::load(fileDir / toPath("TestModelUserScript2_2.ossr"));
  ASSERT_TRUE(temp);
  result = temp.get();
  EXPECT_TRUE(result.value() == OSResultValue::Fail);
  EXPECT_EQ(1u,result.errors().size()); // object not in model
  EXPECT_EQ(0u,result.warnings().size());
  EXPECT_EQ(0u,result.info().size());
  EXPECT_FALSE(result.initialCondition());
  EXPECT_FALSE(result.finalCondition());

  temp = OSResult::load(fileDir / toPath("TestModelUserScript2_3.ossr"));
  ASSERT_TRUE(temp);
  result = temp.get();
  EXPECT_TRUE(result.value() == OSResultValue::NA);
  EXPECT_EQ(0u,result.errors().size());
  EXPECT_EQ(0u,result.warnings().size());
  EXPECT_EQ(1u,result.info().size()); // Measure not applicable as called
  EXPECT_FALSE(result.initialCondition());
  EXPECT_FALSE(result.finalCondition());

  temp = OSResult::load(fileDir / toPath("TestModelUserScript2_4.ossr"));
  ASSERT_TRUE(temp);
  result = temp.get();
  EXPECT_TRUE(result.value() == OSResultValue::Success);
  EXPECT_EQ(0u,result.errors().size());
  EXPECT_EQ(0u,result.warnings().size());
  EXPECT_EQ(0u,result.info().size());
  EXPECT_TRUE(result.initialCondition()); // describes original state
  EXPECT_TRUE(result.finalCondition());   // describes changes

  temp = OSResult::load(fileDir / toPath("TestModelUserScript2_5.ossr"));
  ASSERT_TRUE(temp);
  result = temp.get();
  EXPECT_TRUE(result.value() == OSResultValue::Success);
  EXPECT_EQ(0u,result.errors().size());
  EXPECT_EQ(0u,result.warnings().size());
  EXPECT_EQ(0u,result.info().size());
  EXPECT_TRUE(result.initialCondition()); // describes original state
  EXPECT_TRUE(result.finalCondition());   // describes changes

  // check that can load attribute jsons
  std::vector<Attribute> loadedAttributes;
  NameFinder<Attribute> lightsDefinitionFinder("lights_definition",true);
  NameFinder<Attribute> multiplierFinder("multiplier",true);
  NameFinder<Attribute> lightsDefinitionNameFinder("lights_definition_name",true);
  NameFinder<Attribute> lpdInFinder("lpd_in",true);
  NameFinder<Attribute> lpdOutFinder("lpd_out",true);
  NameFinder<Attribute> lightsDefinitionNumInstancesFinder("lights_definition_num_instances",true);
  NameFinder<Attribute> lightsDefinitionFloorAreaFinder("lights_definition_floor_area",true);
  NameFinder<Attribute> lightsDefinitionFloorAreaIPFinder("lights_definition_floor_area_ip",true);
  AttributeVector::const_iterator it;

  // lights definition not in model - load attributes
  loadedAttributes = toVectorOfAttribute(fileDir / toPath("TestModelUserScript2_2.json"));
  EXPECT_EQ(2u,loadedAttributes.size());
  // lights_definition
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::String);
  EXPECT_FALSE(it->valueAsString().empty());
  // multiplier
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),multiplierFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  EXPECT_DOUBLE_EQ(0.8,it->valueAsDouble());
  EXPECT_FALSE(it->units());

  // run with bad lights definition type - load attributes
  loadedAttributes = toVectorOfAttribute(fileDir / toPath("TestModelUserScript2_3.json"));
  EXPECT_EQ(3u,loadedAttributes.size());
  // lights_definition
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::String);
  EXPECT_FALSE(it->valueAsString().empty());
  // multiplier
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),multiplierFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  EXPECT_DOUBLE_EQ(0.8,it->valueAsDouble());
  EXPECT_FALSE(it->units());
  // lights_definition_name
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionNameFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::String);
  EXPECT_FALSE(it->valueAsString().empty());

  // good run, default multiplier
  loadedAttributes = toVectorOfAttribute(fileDir / toPath("TestModelUserScript2_4.json"));
  EXPECT_EQ(8u,loadedAttributes.size());
  // lights_definition
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::String);
  EXPECT_FALSE(it->valueAsString().empty());
  // multiplier
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),multiplierFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  EXPECT_DOUBLE_EQ(0.8,it->valueAsDouble());
  EXPECT_FALSE(it->units());
  // lights_definition_name
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionNameFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::String);
  EXPECT_FALSE(it->valueAsString().empty());
  // lpd_in
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lpdInFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  EXPECT_DOUBLE_EQ(10.0,it->valueAsDouble());
  ASSERT_TRUE(it->units());
  EXPECT_EQ("W/m^2",it->units().get());
  // -- unit conversion example --
  OptionalDouble ipValue = convert(it->valueAsDouble(),it->units().get(),"W/ft^2");
  ASSERT_TRUE(ipValue);
  EXPECT_DOUBLE_EQ(0.9290304,*ipValue);
  // lpd_out
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lpdOutFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  EXPECT_DOUBLE_EQ(8.0,it->valueAsDouble());
  ASSERT_TRUE(it->units());
  EXPECT_EQ("W/m^2",it->units().get());
  // lights_definition_num_instances
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionNumInstancesFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  //EXPECT_TRUE(it->valueType() == AttributeValueType::Integer); // JSON does not distinguish between int and float
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  // lights_definition_floor_area
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionFloorAreaFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  ASSERT_TRUE(it->units());
  EXPECT_EQ("m^2",it->units().get());
  // lights_definition_floor_area_ip
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionFloorAreaIPFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  ASSERT_TRUE(it->units());
  EXPECT_EQ("ft^2",it->units().get());

  // good run, different multiplier
  loadedAttributes = toVectorOfAttribute(fileDir / toPath("TestModelUserScript2_5.json"));
  EXPECT_EQ(8u,loadedAttributes.size());
  // lights_definition
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::String);
  EXPECT_FALSE(it->valueAsString().empty());
  // multiplier
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),multiplierFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  EXPECT_DOUBLE_EQ(0.5,it->valueAsDouble());
  EXPECT_FALSE(it->units());
  // lights_definition_name
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionNameFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::String);
  EXPECT_FALSE(it->valueAsString().empty());
  // lpd_in
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lpdInFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  EXPECT_DOUBLE_EQ(8.0,it->valueAsDouble()); // uses previous example _out as _in
  ASSERT_TRUE(it->units());
  EXPECT_EQ("W/m^2",it->units().get());
  // -- unit conversion example --
  ipValue = convert(it->valueAsDouble(),it->units().get(),"W/ft^2");
  ASSERT_TRUE(ipValue);
  EXPECT_DOUBLE_EQ(0.74322432,*ipValue);
  // lpd_out
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lpdOutFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  EXPECT_DOUBLE_EQ(4.0,it->valueAsDouble());
  ASSERT_TRUE(it->units());
  EXPECT_EQ("W/m^2",it->units().get());
  // lights_definition_num_instances
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionNumInstancesFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  //EXPECT_TRUE(it->valueType() == AttributeValueType::Integer); // JSON does not distinguish between int and float
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  // lights_definition_floor_area
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionFloorAreaFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  ASSERT_TRUE(it->units());
  EXPECT_EQ("m^2",it->units().get());
  // lights_definition_floor_area_ip
  it = std::find_if(loadedAttributes.begin(),loadedAttributes.end(),lightsDefinitionFloorAreaIPFinder);
  ASSERT_FALSE(it == loadedAttributes.end());
  EXPECT_TRUE(it->valueType() == AttributeValueType::Double);
  ASSERT_TRUE(it->units());
  EXPECT_EQ("ft^2",it->units().get());
}