<commit_before>// Copyright 2010-2012 Google
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This model implements a simple jobshop problem with
// earlyness-tardiness costs.
//
// A earlyness-tardinessjobshop is a standard scheduling problem where
// you must schedule a set of jobs on a set of machines.  Each job is
// a sequence of tasks (a task can only start when the preceding task
// finished), each of which occupies a single specific machine during
// a specific duration. Therefore, a job is a sequence of pairs
// (machine id, duration), along with a release data (minimum start
// date of the first task of the job, and due data (end time of the
// last job) with a tardiness linear penalty.

// The objective is to minimize the sum of early-tardy penalties for each job.
//
// This will be modelled by sets of intervals variables (see class
// IntervalVar in constraint_solver/constraint_solver.h), one per
// task, representing the [start_time, end_time] of the task.  Tasks
// in the same job will be linked by precedence constraints.  Tasks on
// the same machine will be covered by Sequence constraints.

#include <cstdio>
#include <cstdlib>

#include <vector>

#include "base/commandlineflags.h"
#include "base/commandlineflags.h"
#include "base/integral_types.h"
#include "base/logging.h"
#include "base/stringprintf.h"
#include "constraint_solver/constraint_solver.h"
#include "linear_solver/linear_solver.h"
#include "util/string_array.h"
#include "cpp/jobshop_earlytardy.h"

DEFINE_string(
    jet_file,
    "",
    "Required: input file description the scheduling problem to solve, "
    "in our jet format:\n"
    "  - the first line is \"<number of jobs> <number of machines>\"\n"
    "  - then one line per job, with a single space-separated "
    "list of \"<machine index> <duration>\", ended by due_date, early_cost,"
    "late_cost\n"
    "note: jobs with one task are not supported");
DEFINE_int32(machine_count, 10, "Machine count");
DEFINE_int32(job_count, 10, "Job count");
DEFINE_int32(max_release_date, 0, "Max release date");
DEFINE_int32(max_early_cost, 0, "Max earlyness weight");
DEFINE_int32(max_tardy_cost, 3, "Max tardiness weight");
DEFINE_int32(max_duration, 10, "Max duration of a task");
DEFINE_int32(scale_factor, 130, "Scale factor (in percent)");
DEFINE_int32(seed, 1, "Random seed");
DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, 0 means no limit.");
DEFINE_bool(time_placement, false, "Use MIP based time placement");
DECLARE_bool(log_prefix);

namespace operations_research {
class TimePlacement : public DecisionBuilder {
 public:
  TimePlacement(const EtJobShopData& data,
                const std::vector<SequenceVar*>& all_sequences,
                const std::vector<std::vector<IntervalVar*> >& jobs_to_tasks)
      : data_(data),
        all_sequences_(all_sequences),
        jobs_to_tasks_(jobs_to_tasks),
        horizon_(data_.horizon()),
        mp_solver_("TimePlacement", MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING),
        num_tasks_(0) {
    for (int i = 0; i < jobs_to_tasks_.size(); ++i) {
      num_tasks_ += jobs_to_tasks_[i].size();
    }
  }

  virtual ~TimePlacement() {}

  virtual Decision* Next(Solver* const solver) {
    mp_solver_.Clear();
    std::vector<std::vector<MPVariable*> > all_vars;
    hash_map<IntervalVar*, MPVariable*> mapping;
    const double infinity = mp_solver_.infinity();
    all_vars.resize(all_sequences_.size());

    // Creates the MP Variables.
    for (int s = 0; s < jobs_to_tasks_.size(); ++s) {
      for (int t = 0; t < jobs_to_tasks_[s].size(); ++t) {
        IntervalVar* const task = jobs_to_tasks_[s][t];
        const string name = StringPrintf("J%dT%d", s, t);
        MPVariable* const var =
            mp_solver_.MakeIntVar(task->StartMin(), task->StartMax(), name);
        mapping[task] = var;
      }
    }

      // Adds the jobs precedence constraints.
    for (int j = 0; j < jobs_to_tasks_.size(); ++j) {
      for (int t = 0; t < jobs_to_tasks_[j].size() - 1; ++t) {
        IntervalVar* const first_task = jobs_to_tasks_[j][t];
        const int duration = first_task->DurationMax();
        IntervalVar* const second_task = jobs_to_tasks_[j][t + 1];
        MPVariable* const first_var = mapping[first_task];
        MPVariable* const second_var = mapping[second_task];
        MPConstraint* const ct =
            mp_solver_.MakeRowConstraint(duration, infinity);
        ct->SetCoefficient(second_var, 1.0);
        ct->SetCoefficient(first_var, -1.0);
      }
    }

    // Adds the ranked machines constraints.
    for (int s = 0; s < all_sequences_.size(); ++s) {
      SequenceVar* const sequence = all_sequences_[s];
      std::vector<int> rank_firsts;
      std::vector<int> rank_lasts;
      std::vector<int> unperformed;
      sequence->FillSequence(&rank_firsts, &rank_lasts, &unperformed);
      CHECK_EQ(0, rank_lasts.size());
      CHECK_EQ(0, unperformed.size());
      for (int i = 0; i < rank_firsts.size() - 1; ++i) {
        IntervalVar* const first_task = sequence->Interval(rank_firsts[i]);
        const int duration = first_task->DurationMax();
        IntervalVar* const second_task = sequence->Interval(rank_firsts[i + 1]);
        MPVariable* const first_var = mapping[first_task];
        MPVariable* const second_var = mapping[second_task];
        MPConstraint* const ct =
            mp_solver_.MakeRowConstraint(duration, infinity);
        ct->SetCoefficient(second_var, 1.0);
        ct->SetCoefficient(first_var, -1.0);
      }
    }

    // Creates penalty terms
    std::vector<MPVariable*> terms;
    mp_solver_.MakeIntVarArray(jobs_to_tasks_.size(),
                               0,
                               infinity,
                               "terms",
                               &terms);
    for (int j = 0; j < jobs_to_tasks_.size(); ++j) {
      mp_solver_.MutableObjective()->SetCoefficient(terms[j], 1.0);
    }
    mp_solver_.MutableObjective()->SetMinimization();
    // Forces penalty terms to be above late and early costs.
    for (int j = 0; j < jobs_to_tasks_.size(); ++j) {
      IntervalVar* const last_task = jobs_to_tasks_[j].back();
      const int duration = last_task->DurationMin();
      MPVariable* const mp_start = mapping[last_task];
      const Job& job = data_.GetJob(j);
      const int ideal_start = job.due_date - duration;
      const int early_offset = job.early_cost * ideal_start;
      MPConstraint* const early_ct =
          mp_solver_.MakeRowConstraint(early_offset, infinity);
      early_ct->SetCoefficient(terms[j], 1);
      early_ct->SetCoefficient(mp_start, job.early_cost);

      const int tardy_offset = job.tardy_cost * ideal_start;
      MPConstraint* const tardy_ct =
          mp_solver_.MakeRowConstraint(-tardy_offset, infinity);
      tardy_ct->SetCoefficient(terms[j], 1);
      tardy_ct->SetCoefficient(mp_start, -job.tardy_cost);
    }

    // Sets minization and solve.
    CHECK_EQ(MPSolver::OPTIMAL, mp_solver_.Solve());
    LOG(INFO) << "MP cost = " << mp_solver_.objective_value();
    for (int j = 0; j < jobs_to_tasks_.size(); ++j) {
      for (int t = 0; t < jobs_to_tasks_[j].size(); ++t) {
        IntervalVar* const first_task = jobs_to_tasks_[j][t];
        MPVariable* const first_var = mapping[first_task];
        const int date = first_var->solution_value();
        first_task->SetStartRange(date, date);
      }
    }
    return NULL;
  }

  virtual string DebugString() const {
    return "TimePlacement";
  }

 private:
  const EtJobShopData& data_;
  const std::vector<SequenceVar*>& all_sequences_;
  const std::vector<std::vector<IntervalVar*> >& jobs_to_tasks_;
  const int horizon_;
  MPSolver mp_solver_;
  int num_tasks_;
};

void EtJobShop(const EtJobShopData& data) {
  Solver solver("et_jobshop");
  const int machine_count = data.machine_count();
  const int job_count = data.job_count();
  const int horizon = data.horizon();

  // ----- Creates all Intervals and vars -----

  // Stores all tasks attached interval variables per job.
  std::vector<std::vector<IntervalVar*> > jobs_to_tasks(job_count);
  // machines_to_tasks stores the same interval variables as above, but
  // grouped my machines instead of grouped by jobs.
  std::vector<std::vector<IntervalVar*> > machines_to_tasks(machine_count);

  // Creates all individual interval variables.
  for (int job_id = 0; job_id < job_count; ++job_id) {
    const Job& job = data.GetJob(job_id);
    const std::vector<Task>& tasks = job.all_tasks;
    for (int task_index = 0; task_index < tasks.size(); ++task_index) {
      const Task& task = tasks[task_index];
      CHECK_EQ(job_id, task.job_id);
      const string name = StringPrintf("J%dM%dI%dD%d",
                                       task.job_id,
                                       task.machine_id,
                                       task_index,
                                       task.duration);
      IntervalVar* const one_task =
          solver.MakeFixedDurationIntervalVar(0,
                                              horizon,
                                              task.duration,
                                              false,
                                              name);
      jobs_to_tasks[task.job_id].push_back(one_task);
      machines_to_tasks[task.machine_id].push_back(one_task);
    }
  }

  // ----- Creates model -----

  // Creates precedences inside jobs.
  for (int job_id = 0; job_id < job_count; ++job_id) {
    const int task_count = jobs_to_tasks[job_id].size();
    for (int task_index = 0; task_index < task_count - 1; ++task_index) {
      IntervalVar* const t1 = jobs_to_tasks[job_id][task_index];
      IntervalVar* const t2 = jobs_to_tasks[job_id][task_index + 1];
      Constraint* const prec =
          solver.MakeIntervalVarRelation(t2, Solver::STARTS_AFTER_END, t1);
      solver.AddConstraint(prec);
    }
  }

  // Add release date.
  for (int job_id = 0; job_id < job_count; ++job_id) {
    const Job& job = data.GetJob(job_id);
    IntervalVar* const t = jobs_to_tasks[job_id][0];
    Constraint* const prec =
        solver.MakeIntervalVarRelation(t,
                                       Solver::STARTS_AFTER,
                                       job.release_date);
    solver.AddConstraint(prec);
  }

  std::vector<IntVar*> penalties;
  for (int job_id = 0; job_id < job_count; ++job_id) {
    const Job& job = data.GetJob(job_id);
    IntervalVar* const t = jobs_to_tasks[job_id][machine_count - 1];
    IntVar* const penalty =
        solver.MakeConvexPiecewiseExpr(t->EndExpr(),
                                       job.early_cost,
                                       job.due_date,
                                       job.due_date,
                                       job.tardy_cost)->Var();
    penalties.push_back(penalty);
  }

  // Adds disjunctive constraints on unary resources.
  for (int machine_id = 0; machine_id < machine_count; ++machine_id) {
    solver.AddConstraint(
        solver.MakeDisjunctiveConstraint(machines_to_tasks[machine_id]));
  }

  // Creates sequences variables on machines. A sequence variable is a
  // dedicated variable whose job is to sequence interval variables.
  std::vector<SequenceVar*> all_sequences;
  for (int machine_id = 0; machine_id < machine_count; ++machine_id) {
    const string name = StringPrintf("Machine_%d", machine_id);
    SequenceVar* const sequence =
        solver.MakeSequenceVar(machines_to_tasks[machine_id], name);
    all_sequences.push_back(sequence);
  }

  // Objective: minimize the weighted penalties.
  IntVar* const objective_var = solver.MakeSum(penalties)->Var();
  OptimizeVar* const objective_monitor = solver.MakeMinimize(objective_var, 1);

  // ----- Search monitors and decision builder -----

  // This decision builder will rank all tasks on all machines.
  DecisionBuilder* const sequence_phase =
      solver.MakePhase(all_sequences, Solver::SEQUENCE_DEFAULT);

  // After the ranking of tasks, the schedule is still loose and any
  // task can be postponed at will. But, because the problem is now a PERT
  // (http://en.wikipedia.org/wiki/Program_Evaluation_and_Review_Technique),
  // we can schedule each task at its earliest start time. This is
  // conveniently done by fixing the objective variable to its
  // minimum value.
  DecisionBuilder* const obj_phase =
      FLAGS_time_placement ?
      solver.RevAlloc(new TimePlacement(data, all_sequences, jobs_to_tasks)) :
      solver.MakePhase(objective_var,
                       Solver::CHOOSE_FIRST_UNBOUND,
                       Solver::ASSIGN_MIN_VALUE);

  // The main decision builder (ranks all tasks, then fixes the
  // objective_variable).
  DecisionBuilder* const main_phase =
      solver.Compose(sequence_phase, obj_phase);

  // Search log.
  const int kLogFrequency = 1000000;
  SearchMonitor* const search_log =
      solver.MakeSearchLog(kLogFrequency, objective_monitor);

  SearchLimit* limit = NULL;
  if (FLAGS_time_limit_in_ms > 0) {
    limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms);
  }

  // Search.
  solver.Solve(main_phase, search_log, objective_monitor, limit);
}
}  // namespace operations_research

static const char kUsage[] =
    "Usage: see flags.\nThis program runs a simple job shop optimization "
    "output besides the debug LOGs of the solver.";

int main(int argc, char **argv) {
  FLAGS_log_prefix = false;
  google::SetUsageMessage(kUsage);
  google::ParseCommandLineFlags(&argc, &argv, true);
  operations_research::EtJobShopData data;
  if (!FLAGS_jet_file.empty()) {
    data.LoadJetFile(FLAGS_jet_file);
  } else {
    data.GenerateRandomData(FLAGS_machine_count,
                            FLAGS_job_count,
                            FLAGS_max_release_date,
                            FLAGS_max_early_cost,
                            FLAGS_max_tardy_cost,
                            FLAGS_max_duration,
                            FLAGS_scale_factor,
                            FLAGS_seed);
  }
  operations_research::EtJobShop(data);
  return 0;
}
<commit_msg>implement time placement code for early tardy<commit_after>// Copyright 2010-2012 Google
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This model implements a simple jobshop problem with
// earlyness-tardiness costs.
//
// A earlyness-tardinessjobshop is a standard scheduling problem where
// you must schedule a set of jobs on a set of machines.  Each job is
// a sequence of tasks (a task can only start when the preceding task
// finished), each of which occupies a single specific machine during
// a specific duration. Therefore, a job is a sequence of pairs
// (machine id, duration), along with a release data (minimum start
// date of the first task of the job, and due data (end time of the
// last job) with a tardiness linear penalty.

// The objective is to minimize the sum of early-tardy penalties for each job.
//
// This will be modelled by sets of intervals variables (see class
// IntervalVar in constraint_solver/constraint_solver.h), one per
// task, representing the [start_time, end_time] of the task.  Tasks
// in the same job will be linked by precedence constraints.  Tasks on
// the same machine will be covered by Sequence constraints.

#include <cstdio>
#include <cstdlib>

#include <vector>

#include "base/commandlineflags.h"
#include "base/commandlineflags.h"
#include "base/integral_types.h"
#include "base/logging.h"
#include "base/stringprintf.h"
#include "constraint_solver/constraint_solver.h"
#include "linear_solver/linear_solver.h"
#include "util/string_array.h"
#include "cpp/jobshop_earlytardy.h"

DEFINE_string(
    jet_file,
    "",
    "Required: input file description the scheduling problem to solve, "
    "in our jet format:\n"
    "  - the first line is \"<number of jobs> <number of machines>\"\n"
    "  - then one line per job, with a single space-separated "
    "list of \"<machine index> <duration>\", ended by due_date, early_cost,"
    "late_cost\n"
    "note: jobs with one task are not supported");
DEFINE_int32(machine_count, 10, "Machine count");
DEFINE_int32(job_count, 10, "Job count");
DEFINE_int32(max_release_date, 0, "Max release date");
DEFINE_int32(max_early_cost, 0, "Max earlyness weight");
DEFINE_int32(max_tardy_cost, 3, "Max tardiness weight");
DEFINE_int32(max_duration, 10, "Max duration of a task");
DEFINE_int32(scale_factor, 130, "Scale factor (in percent)");
DEFINE_int32(seed, 1, "Random seed");
DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, 0 means no limit.");
DEFINE_bool(time_placement, false, "Use MIP based time placement");
DECLARE_bool(log_prefix);

namespace operations_research {
class TimePlacement : public DecisionBuilder {
 public:
  TimePlacement(const EtJobShopData& data,
                const std::vector<SequenceVar*>& all_sequences,
                const std::vector<std::vector<IntervalVar*> >& jobs_to_tasks)
      : data_(data),
        all_sequences_(all_sequences),
        jobs_to_tasks_(jobs_to_tasks),
        horizon_(data_.horizon()),
        mp_solver_("TimePlacement", MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING),
        num_tasks_(0) {
    for (int i = 0; i < jobs_to_tasks_.size(); ++i) {
      num_tasks_ += jobs_to_tasks_[i].size();
    }
  }

  virtual ~TimePlacement() {}

  virtual Decision* Next(Solver* const solver) {
    mp_solver_.Clear();
    std::vector<std::vector<MPVariable*> > all_vars;
    hash_map<IntervalVar*, MPVariable*> mapping;
    const double infinity = mp_solver_.infinity();
    all_vars.resize(all_sequences_.size());

    // Creates the MP Variables.
    for (int s = 0; s < jobs_to_tasks_.size(); ++s) {
      for (int t = 0; t < jobs_to_tasks_[s].size(); ++t) {
        IntervalVar* const task = jobs_to_tasks_[s][t];
        const string name = StringPrintf("J%dT%d", s, t);
        MPVariable* const var =
            mp_solver_.MakeIntVar(task->StartMin(), task->StartMax(), name);
        mapping[task] = var;
      }
    }

      // Adds the jobs precedence constraints.
    for (int j = 0; j < jobs_to_tasks_.size(); ++j) {
      for (int t = 0; t < jobs_to_tasks_[j].size() - 1; ++t) {
        IntervalVar* const first_task = jobs_to_tasks_[j][t];
        const int duration = first_task->DurationMax();
        IntervalVar* const second_task = jobs_to_tasks_[j][t + 1];
        MPVariable* const first_var = mapping[first_task];
        MPVariable* const second_var = mapping[second_task];
        MPConstraint* const ct =
            mp_solver_.MakeRowConstraint(duration, infinity);
        ct->SetCoefficient(second_var, 1.0);
        ct->SetCoefficient(first_var, -1.0);
      }
    }

    // Adds the ranked machines constraints.
    for (int s = 0; s < all_sequences_.size(); ++s) {
      SequenceVar* const sequence = all_sequences_[s];
      std::vector<int> rank_firsts;
      std::vector<int> rank_lasts;
      std::vector<int> unperformed;
      sequence->FillSequence(&rank_firsts, &rank_lasts, &unperformed);
      CHECK_EQ(0, rank_lasts.size());
      CHECK_EQ(0, unperformed.size());
      for (int i = 0; i < rank_firsts.size() - 1; ++i) {
        IntervalVar* const first_task = sequence->Interval(rank_firsts[i]);
        const int duration = first_task->DurationMax();
        IntervalVar* const second_task = sequence->Interval(rank_firsts[i + 1]);
        MPVariable* const first_var = mapping[first_task];
        MPVariable* const second_var = mapping[second_task];
        MPConstraint* const ct =
            mp_solver_.MakeRowConstraint(duration, infinity);
        ct->SetCoefficient(second_var, 1.0);
        ct->SetCoefficient(first_var, -1.0);
      }
    }

    // Creates penalty terms and objective.
    std::vector<MPVariable*> terms;
    mp_solver_.MakeIntVarArray(jobs_to_tasks_.size(),
                               0,
                               infinity,
                               "terms",
                               &terms);
    for (int j = 0; j < jobs_to_tasks_.size(); ++j) {
      mp_solver_.MutableObjective()->SetCoefficient(terms[j], 1.0);
    }
    mp_solver_.MutableObjective()->SetMinimization();

    // Forces penalty terms to be above late and early costs.
    for (int j = 0; j < jobs_to_tasks_.size(); ++j) {
      IntervalVar* const last_task = jobs_to_tasks_[j].back();
      const int duration = last_task->DurationMin();
      MPVariable* const mp_start = mapping[last_task];
      const Job& job = data_.GetJob(j);
      const int ideal_start = job.due_date - duration;
      const int early_offset = job.early_cost * ideal_start;
      MPConstraint* const early_ct =
          mp_solver_.MakeRowConstraint(early_offset, infinity);
      early_ct->SetCoefficient(terms[j], 1);
      early_ct->SetCoefficient(mp_start, job.early_cost);

      const int tardy_offset = job.tardy_cost * ideal_start;
      MPConstraint* const tardy_ct =
          mp_solver_.MakeRowConstraint(-tardy_offset, infinity);
      tardy_ct->SetCoefficient(terms[j], 1);
      tardy_ct->SetCoefficient(mp_start, -job.tardy_cost);
    }

    // Solve.
    CHECK_EQ(MPSolver::OPTIMAL, mp_solver_.Solve());

    // Inject MIP solution into the CP part.
    LOG(INFO) << "MP cost = " << mp_solver_.objective_value();
    for (int j = 0; j < jobs_to_tasks_.size(); ++j) {
      for (int t = 0; t < jobs_to_tasks_[j].size(); ++t) {
        IntervalVar* const first_task = jobs_to_tasks_[j][t];
        MPVariable* const first_var = mapping[first_task];
        const int date = first_var->solution_value();
        first_task->SetStartRange(date, date);
      }
    }
    return NULL;
  }

  virtual string DebugString() const {
    return "TimePlacement";
  }

 private:
  const EtJobShopData& data_;
  const std::vector<SequenceVar*>& all_sequences_;
  const std::vector<std::vector<IntervalVar*> >& jobs_to_tasks_;
  const int horizon_;
  MPSolver mp_solver_;
  int num_tasks_;
};

void EtJobShop(const EtJobShopData& data) {
  Solver solver("et_jobshop");
  const int machine_count = data.machine_count();
  const int job_count = data.job_count();
  const int horizon = data.horizon();

  // ----- Creates all Intervals and vars -----

  // Stores all tasks attached interval variables per job.
  std::vector<std::vector<IntervalVar*> > jobs_to_tasks(job_count);
  // machines_to_tasks stores the same interval variables as above, but
  // grouped my machines instead of grouped by jobs.
  std::vector<std::vector<IntervalVar*> > machines_to_tasks(machine_count);

  // Creates all individual interval variables.
  for (int job_id = 0; job_id < job_count; ++job_id) {
    const Job& job = data.GetJob(job_id);
    const std::vector<Task>& tasks = job.all_tasks;
    for (int task_index = 0; task_index < tasks.size(); ++task_index) {
      const Task& task = tasks[task_index];
      CHECK_EQ(job_id, task.job_id);
      const string name = StringPrintf("J%dM%dI%dD%d",
                                       task.job_id,
                                       task.machine_id,
                                       task_index,
                                       task.duration);
      IntervalVar* const one_task =
          solver.MakeFixedDurationIntervalVar(0,
                                              horizon,
                                              task.duration,
                                              false,
                                              name);
      jobs_to_tasks[task.job_id].push_back(one_task);
      machines_to_tasks[task.machine_id].push_back(one_task);
    }
  }

  // ----- Creates model -----

  // Creates precedences inside jobs.
  for (int job_id = 0; job_id < job_count; ++job_id) {
    const int task_count = jobs_to_tasks[job_id].size();
    for (int task_index = 0; task_index < task_count - 1; ++task_index) {
      IntervalVar* const t1 = jobs_to_tasks[job_id][task_index];
      IntervalVar* const t2 = jobs_to_tasks[job_id][task_index + 1];
      Constraint* const prec =
          solver.MakeIntervalVarRelation(t2, Solver::STARTS_AFTER_END, t1);
      solver.AddConstraint(prec);
    }
  }

  // Add release date.
  for (int job_id = 0; job_id < job_count; ++job_id) {
    const Job& job = data.GetJob(job_id);
    IntervalVar* const t = jobs_to_tasks[job_id][0];
    Constraint* const prec =
        solver.MakeIntervalVarRelation(t,
                                       Solver::STARTS_AFTER,
                                       job.release_date);
    solver.AddConstraint(prec);
  }

  std::vector<IntVar*> penalties;
  for (int job_id = 0; job_id < job_count; ++job_id) {
    const Job& job = data.GetJob(job_id);
    IntervalVar* const t = jobs_to_tasks[job_id][machine_count - 1];
    IntVar* const penalty =
        solver.MakeConvexPiecewiseExpr(t->EndExpr(),
                                       job.early_cost,
                                       job.due_date,
                                       job.due_date,
                                       job.tardy_cost)->Var();
    penalties.push_back(penalty);
  }

  // Adds disjunctive constraints on unary resources.
  for (int machine_id = 0; machine_id < machine_count; ++machine_id) {
    solver.AddConstraint(
        solver.MakeDisjunctiveConstraint(machines_to_tasks[machine_id]));
  }

  // Creates sequences variables on machines. A sequence variable is a
  // dedicated variable whose job is to sequence interval variables.
  std::vector<SequenceVar*> all_sequences;
  for (int machine_id = 0; machine_id < machine_count; ++machine_id) {
    const string name = StringPrintf("Machine_%d", machine_id);
    SequenceVar* const sequence =
        solver.MakeSequenceVar(machines_to_tasks[machine_id], name);
    all_sequences.push_back(sequence);
  }

  // Objective: minimize the weighted penalties.
  IntVar* const objective_var = solver.MakeSum(penalties)->Var();
  OptimizeVar* const objective_monitor = solver.MakeMinimize(objective_var, 1);

  // ----- Search monitors and decision builder -----

  // This decision builder will rank all tasks on all machines.
  DecisionBuilder* const sequence_phase =
      solver.MakePhase(all_sequences, Solver::SEQUENCE_DEFAULT);

  // After the ranking of tasks, the schedule is still loose and any
  // task can be postponed at will. But, because the problem is now a PERT
  // (http://en.wikipedia.org/wiki/Program_Evaluation_and_Review_Technique),
  // we can schedule each task at its earliest start time. This is
  // conveniently done by fixing the objective variable to its
  // minimum value.
  DecisionBuilder* const obj_phase =
      FLAGS_time_placement ?
      solver.RevAlloc(new TimePlacement(data, all_sequences, jobs_to_tasks)) :
      solver.MakePhase(objective_var,
                       Solver::CHOOSE_FIRST_UNBOUND,
                       Solver::ASSIGN_MIN_VALUE);

  // The main decision builder (ranks all tasks, then fixes the
  // objective_variable).
  DecisionBuilder* const main_phase =
      solver.Compose(sequence_phase, obj_phase);

  // Search log.
  const int kLogFrequency = 1000000;
  SearchMonitor* const search_log =
      solver.MakeSearchLog(kLogFrequency, objective_monitor);

  SearchLimit* limit = NULL;
  if (FLAGS_time_limit_in_ms > 0) {
    limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms);
  }

  // Search.
  solver.Solve(main_phase, search_log, objective_monitor, limit);
}
}  // namespace operations_research

static const char kUsage[] =
    "Usage: see flags.\nThis program runs a simple job shop optimization "
    "output besides the debug LOGs of the solver.";

int main(int argc, char **argv) {
  FLAGS_log_prefix = false;
  google::SetUsageMessage(kUsage);
  google::ParseCommandLineFlags(&argc, &argv, true);
  operations_research::EtJobShopData data;
  if (!FLAGS_jet_file.empty()) {
    data.LoadJetFile(FLAGS_jet_file);
  } else {
    data.GenerateRandomData(FLAGS_machine_count,
                            FLAGS_job_count,
                            FLAGS_max_release_date,
                            FLAGS_max_early_cost,
                            FLAGS_max_tardy_cost,
                            FLAGS_max_duration,
                            FLAGS_scale_factor,
                            FLAGS_seed);
  }
  operations_research::EtJobShop(data);
  return 0;
}
<|endoftext|>


<commit_before>/**
  \file   linear.hh
  \brief  Contains linear operator related classes.
  **/

#ifndef DUNE_FEM_FUNCTIONALS_OPERATOR_ELLIPTICFINITEELEMENT_HH
#define DUNE_FEM_FUNCTIONALS_OPERATOR_ELLIPTICFINITEELEMENT_HH

// dune fem includes
#include <dune/fem/function/common/function.hh>
#include <dune/fem/quadrature/cachingquadrature.hh>

// dune-fem-functionals includes
#include <dune/fem/common/localmatrix.hh>
#include <dune/fem/common/localvector.hh>

namespace Dune {

//! dune-fem-functionals
namespace Functionals {

//! Contains several operators.
namespace DiscreteOperator {

namespace Local {

/**
  \brief  Linear operator.

          This class represents a linear operator \f$A: V_{h} \rightarrow W_{h}^{-1}\f$ which arises from a weak
          formulation, where \f$V_{h}\f$ and \f$W_{h}\f$ are discrete function spaces, i.e. Lagrange finite element
          spaces. These are usually called ansatz and test space. A linear operator induces a bilinear
          form
          \f[
            A: V_h \times W_h \rightarrow \mathbb{R} \text{,}
          \f]
          \f[
            u,v \mapsto A(u)[v] \text{,}
          \f]
          where \f$A(u): W_h \rightarrow \mathbb{R}\f$ itself is a functional for each \f$u \in V_h\f$. Because of the
          linearity of \f$A\f$ we can decompose the the application of \f$A(u)[\psi]\f$ to an argument
          \f$u \in V_{h}\f$, testet by a basefunction \f$\psi \in W_h\f$ into
          local applications of \f$A\f$ to the local basefunctions of the ansatz and test space:
          \f[
            A(u)[\psi] = \sum_{E \in \mathcal{T}_{h}} \sum_{i \in I_{E}} \sum_{j \in J_{E}} u_i A( \varphi_i
  )[\psi_j]\text{,}
          \f]
          where \f$E\f$ are the codim 0 entities of a triangulation \f$\mathcal{T}_{h}\f$, \f$I_E\f$ and \f$J_E\f$ are
          index sets of local DoFs, \f$u_{i}\f$ are the corresponding coefficients, \f$\varphi_i\f$ are the
          local basefunctions of the ansatz space \f$V_{h}\f$ and \f$\psi_j\f$ are the local basefunctions of the
          test space \f$W_h\f$. A linear operator thus only hast to provide this local application
          \f$A( \varphi_i )[\psi_j]\f$. This local application (and thus the linear operator) is induced by a local
          operation \f$a(\varphi,\psi)\f$ which operates on local functions. In the finite element case for example,
          where the operator is given by
          \f[
            A(u)[v] = \int_{\Omega} a \nabla \varphi \nabla \psi \text{dx} \text{,}
          \f]
          for some function \f$a\f$, the corresponding local operation is given by
          \f[
            a(\varphi,\psi) := \int_{E} a(x) \nabla \varphi(x) \nabla \psi(x) \text{dx} \text{.}
          \f]
  \tparam LocalOperationImp
          Type of the local operation \f$a\f$, i.e. Dune::Functionals::LocalOperation::Interface. This local operation
          has to implement the method operate( localTestFunction, localAnsatzFunction ).
  \tparam DiscreteAnsatzFunctionSpaceImp
          Type of the discrete function space \f$V_h\f$, where the ansatz functions live in, i.e.
          Dune::DiscreteFunctionSpaceInterface.
  \tparam DiscreteTestFunctionSpaceImp
          Type of the discrete function space \f$W_h\f$, where the test functions live in, i.e.
          Dune::DiscreteFunctionSpaceInterface. If the test and the ansatz space are identicall, this template
          argument may be omitted.
  **/
template <class LocalEvaluationType, class DiscreteAnsatzFunctionSpaceImp,
          class DiscreteTestFunctionSpaceImp = DiscreteAnsatzFunctionSpaceImp>
class Codim0Integration
{
public:
  typedef DiscreteAnsatzFunctionSpaceImp DiscreteAnsatzFunctionSpaceType;

  typedef DiscreteTestFunctionSpaceImp DiscreteTestFunctionSpaceType;

  typedef typename DiscreteAnsatzFunctionSpaceType::RangeFieldType RangeFieldType;

  typedef Dune::Functionals::Common::LocalMatrix<RangeFieldType> LocalMatrixType;

  typedef typename DiscreteAnsatzFunctionSpaceType::EntityType EntityType;

  typedef typename DiscreteAnsatzFunctionSpaceType::LocalBaseFunctionProviderType LocalAnsatzBaseFunctionProviderType;

  typedef typename DiscreteAnsatzFunctionSpaceType::LocalBaseFunctionProviderType LocalTestBaseFunctionProviderType;

  typedef typename LocalAnsatzBaseFunctionProviderType::LocalBaseFunctionType LocalAnsatzBaseFunctionType;

  typedef typename LocalTestBaseFunctionProviderType::LocalBaseFunctionType LocalTestBaseFunctionType;

  /**
    \brief      Constructor storing the local operation and the ansatz and test space.

                Use this constructor, if ansatz and test space are not the same.
    \param[in]  localOperation
                The local operation \f$a\f$, which induces the operator.
    \param[in]  ansatzSpace
                The space of ansatz functions \f$V_h\f$.
    \param[in]  testSpace
                The space of test functions \f$W_h\f$.
    **/
  Codim0Integration(const LocalEvaluationType& localEvaluation, const DiscreteAnsatzFunctionSpaceType& ansatzSpace,
                    const DiscreteTestFunctionSpaceType& testSpace)
    : localEvaluation_(localEvaluation)
    , ansatzSpace_(ansatzSpace)
    , testSpace_(testSpace)
    , localAnsatzBaseFunctionProvider_(ansatzSpace.localBaseFunctionProvider())
    , localTestBaseFunctionProvider_(testSpace.localBaseFunctionProvider())
  {
  }

  /**
    \brief      Constructor storing the local operation and the ansatz and test space.

                Use this constructor, if ansatz and test space are the same.
    \param[in]  localOperation
                The local operation \f$a\f$, which induces the operator.
    \param[in]  space
                The space of ansatz and test functions \f$V_h = W_h\f$.
    **/
  Codim0Integration(const LocalEvaluationType& localEvaluation, const DiscreteAnsatzFunctionSpaceType& space)
    : localEvaluation_(localEvaluation)
    , ansatzSpace_(space)
    , testSpace_(space)
    , localAnsatzBaseFunctionProvider_(space.localBaseFunctionProvider())
    , localTestBaseFunctionProvider_(space.localBaseFunctionProvider())
  {
  }

  /**
    \brief  Returns the ansatz space \f$V_h\f$.
    \return \f$V_h\f$.
    **/
  const DiscreteAnsatzFunctionSpaceType& ansatzSpace() const
  {
    return ansatzSpace_;
  }

  /**
    \brief  Returns the test space \f$W_h\f$.
    \return \f$W_h\f$.
    **/
  const DiscreteTestFunctionSpaceType& testSpace() const
  {
    return testSpace_;
  }

  /**
    \brief  Returns the local operation \f$a\f$.
    \return \f$a\f$.
    **/
  const LocalEvaluationType& localEvaluation() const
  {
    return localEvaluation_;
  }

  /**
    \brief      Local application of the operator.

                This method represents the application of the operator to all local basefunctions of the ansatz and
                test space on a given entity:
                \f[
                  \{A( \varphi_i )[\psi_j]\}_{i \in I_E, j \in J_E} \text{.}
                \f]
    \param[in]  entity
                The entity, on wich the operator is being applied on.
    \return     The matrix \f$\{A( \varphi_i )[\psi_j]\}_{i \in I_E, j \in J_E}\f$.
    **/
  //  LocalMatrixType applyLocal( const EntityType& entity ) const
  //  {
  //    const unsigned numberOfLocalAnsatzDoFs = ansatzSpace_.baseFunctionSet( entity ).numBaseFunctions();
  //    const unsigned numberOfLocalTestDoFs = testSpace_.baseFunctionSet( entity ).numBaseFunctions();

  //    // init return matrix
  //    LocalMatrixType ret( numberOfLocalAnsatzDoFs, numberOfLocalTestDoFs );

  //    // do loop over all local ansatz DoFs
  //    for( unsigned int i = 0; i < numberOfLocalAnsatzDoFs; ++i )
  //    {
  //      // do loop over all local test DoFs
  //      for( unsigned int j = 0; j < numberOfLocalTestDoFs; ++j )
  //      {
  //        const LocalAnsatzBaseFunctionType localAnsatzBaseFunction_i = localAnsatzBaseFunctionProvider_.provide(
  //        entity, i);
  //        const LocalTestBaseFunctionType localTestBaseFunction_j = localTestBaseFunctionProvider_.provide( entity,
  //        j);

  //        const RangeFieldType operator_i_j = localOperation_.operate( localAnsatzBaseFunction_i,
  //        localTestBaseFunction_j );

  //        // set local matrix
  //        ret[i][j] =  operator_i_j;

  //      } // done loop over all local test DoFs

  //    } // done loop over all local ansatz DoFs

  //    return ret;

  //  }

private:
  const LocalEvaluationType& localEvaluation_;
  const DiscreteAnsatzFunctionSpaceType& ansatzSpace_;
  const DiscreteTestFunctionSpaceType& testSpace_;
  const LocalAnsatzBaseFunctionProviderType& localAnsatzBaseFunctionProvider_;
  const LocalTestBaseFunctionProviderType& localTestBaseFunctionProvider_;

}; // end class Linear

} // end namespace Local

} // end namespace DiscreteOperator

} // end namespace Functionals

} // end namespace Dune

#endif // end DUNE_FEM_FUNCTIONALS_OPERATOR_ELLIPTICFINITEELEMENT_HH
<commit_msg>moved DiscreteOperator::Local from dune/fem/operator/ to dune/functionals/discreteoperator/local/<commit_after>/**
  \file   linear.hh
  \brief  Contains linear operator related classes.
  **/

#ifndef DUNE_FEM_FUNCTIONALS_OPERATOR_ELLIPTICFINITEELEMENT_HH
#define DUNE_FEM_FUNCTIONALS_OPERATOR_ELLIPTICFINITEELEMENT_HH

// dune fem includes
#include <dune/fem/quadrature/cachingquadrature.hh>

// dune-functionals includes
#include <dune/fem/common/localmatrix.hh>
#include <dune/fem/common/localvector.hh>

namespace Dune {

//! dune-fem-functionals
namespace Functionals {

//! Contains several operators.
namespace DiscreteOperator {

namespace Local {

/**
  \todo   correct doc
  \brief  Linear operator.

          This class represents a linear operator \f$A: V_{h} \rightarrow W_{h}^{-1}\f$ which arises from a weak
          formulation, where \f$V_{h}\f$ and \f$W_{h}\f$ are discrete function spaces, i.e. Lagrange finite element
          spaces. These are usually called ansatz and test space. A linear operator induces a bilinear
          form
          \f[
            A: V_h \times W_h \rightarrow \mathbb{R} \text{,}
          \f]
          \f[
            u,v \mapsto A(u)[v] \text{,}
          \f]
          where \f$A(u): W_h \rightarrow \mathbb{R}\f$ itself is a functional for each \f$u \in V_h\f$. Because of the
          linearity of \f$A\f$ we can decompose the the application of \f$A(u)[\psi]\f$ to an argument
          \f$u \in V_{h}\f$, testet by a basefunction \f$\psi \in W_h\f$ into
          local applications of \f$A\f$ to the local basefunctions of the ansatz and test space:
          \f[
            A(u)[\psi] = \sum_{E \in \mathcal{T}_{h}} \sum_{i \in I_{E}} \sum_{j \in J_{E}} u_i A( \varphi_i
  )[\psi_j]\text{,}
          \f]
          where \f$E\f$ are the codim 0 entities of a triangulation \f$\mathcal{T}_{h}\f$, \f$I_E\f$ and \f$J_E\f$ are
          index sets of local DoFs, \f$u_{i}\f$ are the corresponding coefficients, \f$\varphi_i\f$ are the
          local basefunctions of the ansatz space \f$V_{h}\f$ and \f$\psi_j\f$ are the local basefunctions of the
          test space \f$W_h\f$. A linear operator thus only hast to provide this local application
          \f$A( \varphi_i )[\psi_j]\f$. This local application (and thus the linear operator) is induced by a local
          operation \f$a(\varphi,\psi)\f$ which operates on local functions. In the finite element case for example,
          where the operator is given by
          \f[
            A(u)[v] = \int_{\Omega} a \nabla \varphi \nabla \psi \text{dx} \text{,}
          \f]
          for some function \f$a\f$, the corresponding local operation is given by
          \f[
            a(\varphi,\psi) := \int_{E} a(x) \nabla \varphi(x) \nabla \psi(x) \text{dx} \text{.}
          \f]
  \tparam LocalOperationImp
          Type of the local operation \f$a\f$, i.e. Dune::Functionals::LocalOperation::Interface. This local operation
          has to implement the method operate( localTestFunction, localAnsatzFunction ).
  \tparam DiscreteAnsatzFunctionSpaceImp
          Type of the discrete function space \f$V_h\f$, where the ansatz functions live in, i.e.
          Dune::DiscreteFunctionSpaceInterface.
  \tparam DiscreteTestFunctionSpaceImp
          Type of the discrete function space \f$W_h\f$, where the test functions live in, i.e.
          Dune::DiscreteFunctionSpaceInterface. If the test and the ansatz space are identicall, this template
          argument may be omitted.
  **/
template <class LocalEvaluationImp, class DiscreteAnsatzFunctionSpaceImp,
          class DiscreteTestFunctionSpaceImp = DiscreteAnsatzFunctionSpaceImp>
class Codim0Integration
{
public:
  typedef LocalEvaluationImp LocalEvaluationType;

  typedef DiscreteAnsatzFunctionSpaceImp DiscreteAnsatzFunctionSpaceType;

  typedef DiscreteTestFunctionSpaceImp DiscreteTestFunctionSpaceType;

  typedef typename DiscreteAnsatzFunctionSpaceType::RangeFieldType RangeFieldType;

  typedef Dune::Functionals::Common::LocalMatrix<RangeFieldType> LocalMatrixType;

  typedef typename DiscreteAnsatzFunctionSpaceType::EntityType EntityType;

  /**
    \brief      Constructor storing the local operation and the ansatz and test space.

                Use this constructor, if ansatz and test space are not the same.
    \param[in]  localOperation
                The local operation \f$a\f$, which induces the operator.
    \param[in]  ansatzSpace
                The space of ansatz functions \f$V_h\f$.
    \param[in]  testSpace
                The space of test functions \f$W_h\f$.
    **/
  Codim0Integration(const LocalEvaluationType& localEvaluation, const DiscreteAnsatzFunctionSpaceType& ansatzSpace,
                    const DiscreteTestFunctionSpaceType& testSpace)
    : localEvaluation_(localEvaluation)
    , ansatzSpace_(ansatzSpace)
    , testSpace_(testSpace)
  {
  }

  /**
    \brief      Constructor storing the local operation and the ansatz and test space.

                Use this constructor, if ansatz and test space are the same.
    \param[in]  localOperation
                The local operation \f$a\f$, which induces the operator.
    \param[in]  space
                The space of ansatz and test functions \f$V_h = W_h\f$.
    **/
  Codim0Integration(const LocalEvaluationType& localEvaluation, const DiscreteAnsatzFunctionSpaceType& space)
    : localEvaluation_(localEvaluation)
    , ansatzSpace_(space)
    , testSpace_(space)
  {
  }

  /**
    \brief  Returns the local operation \f$a\f$.
    \return \f$a\f$.
    **/
  const LocalEvaluationType localEvaluation() const
  {
    return localEvaluation_;
  }

  /**
    \brief  Returns the ansatz space \f$V_h\f$.
    \return \f$V_h\f$.
    **/
  const DiscreteAnsatzFunctionSpaceType& ansatzSpace() const
  {
    return ansatzSpace_;
  }

  /**
    \brief  Returns the test space \f$W_h\f$.
    \return \f$W_h\f$.
    **/
  const DiscreteTestFunctionSpaceType& testSpace() const
  {
    return testSpace_;
  }

  /**
    \brief      Local application of the operator.

                This method represents the application of the operator to all local basefunctions of the ansatz and
                test space on a given entity:
                \f[
                  \{A( \varphi_i )[\psi_j]\}_{i \in I_E, j \in J_E} \text{.}
                \f]
    \param[in]  entity
                The entity, on wich the operator is being applied on.
    \return     The matrix \f$\{A( \varphi_i )[\psi_j]\}_{i \in I_E, j \in J_E}\f$.
    **/
  //  LocalMatrixType applyLocal( const EntityType& entity ) const
  //  {
  //    const unsigned numberOfLocalAnsatzDoFs = ansatzSpace_.baseFunctionSet( entity ).numBaseFunctions();
  //    const unsigned numberOfLocalTestDoFs = testSpace_.baseFunctionSet( entity ).numBaseFunctions();

  //    // init return matrix
  //    LocalMatrixType ret( numberOfLocalAnsatzDoFs, numberOfLocalTestDoFs );

  //    // do loop over all local ansatz DoFs
  //    for( unsigned int i = 0; i < numberOfLocalAnsatzDoFs; ++i )
  //    {
  //      // do loop over all local test DoFs
  //      for( unsigned int j = 0; j < numberOfLocalTestDoFs; ++j )
  //      {
  //        const LocalAnsatzBaseFunctionType localAnsatzBaseFunction_i = localAnsatzBaseFunctionProvider_.provide(
  //        entity, i);
  //        const LocalTestBaseFunctionType localTestBaseFunction_j = localTestBaseFunctionProvider_.provide( entity,
  //        j);

  //        const RangeFieldType operator_i_j = localOperation_.operate( localAnsatzBaseFunction_i,
  //        localTestBaseFunction_j );

  //        // set local matrix
  //        ret[i][j] =  operator_i_j;

  //      } // done loop over all local test DoFs

  //    } // done loop over all local ansatz DoFs

  //    return ret;
  //  }

private:
  const LocalEvaluationType localEvaluation_;
  const DiscreteAnsatzFunctionSpaceType& ansatzSpace_;
  const DiscreteTestFunctionSpaceType& testSpace_;

}; // end class Codim0Integration

} // end namespace Local

} // end namespace DiscreteOperator

} // end namespace Functionals

} // end namespace Dune

#endif // end DUNE_FEM_FUNCTIONALS_OPERATOR_ELLIPTICFINITEELEMENT_HH
<|endoftext|>


<commit_before>/*-------------------------------------------------------------------------
 * Vulkan Conformance Tests
 * ------------------------
 *
 * Copyright (c) 2015 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Utility for pre-compiling source programs to SPIR-V
 *//*--------------------------------------------------------------------*/

#include "tcuDefs.hpp"
#include "tcuCommandLine.hpp"
#include "tcuPlatform.hpp"
#include "tcuResource.hpp"
#include "tcuTestLog.hpp"
#include "tcuTestHierarchyIterator.hpp"
#include "deUniquePtr.hpp"
#include "vkPrograms.hpp"
#include "vkBinaryRegistry.hpp"
#include "vktTestCase.hpp"
#include "vktTestPackage.hpp"
#include "deUniquePtr.hpp"
#include "deCommandLine.hpp"
#include "deSharedPtr.hpp"

#include <iostream>

using std::vector;
using std::string;
using de::UniquePtr;
using de::MovePtr;
using de::SharedPtr;

namespace vkt
{

tcu::TestPackageRoot* createRoot (tcu::TestContext& testCtx)
{
	vector<tcu::TestNode*>	children;
	children.push_back(new TestPackage(testCtx));
	return new tcu::TestPackageRoot(testCtx, children);
}

struct BuildStats
{
	int		numSucceeded;
	int		numFailed;

	BuildStats (void)
		: numSucceeded	(0)
		, numFailed		(0)
	{
	}
};

namespace // anonymous
{

void writeBuildLogs (const glu::ShaderProgramInfo& buildInfo, std::ostream& dst)
{
	for (size_t shaderNdx = 0; shaderNdx < buildInfo.shaders.size(); shaderNdx++)
	{
		const glu::ShaderInfo&	shaderInfo	= buildInfo.shaders[shaderNdx];
		const char* const		shaderName	= getShaderTypeName(shaderInfo.type);

		dst << shaderName << " source:\n"
			<< "---\n"
			<< shaderInfo.source << "\n"
			<< "---\n"
			<< shaderName << " compile log:\n"
			<< "---\n"
			<< shaderInfo.infoLog << "\n"
			<< "---\n";
	}

	dst << "link log:\n"
		<< "---\n"
		<< buildInfo.program.infoLog << "\n"
		<< "---\n";
}

void writeBuildLogs (const vk::SpirVProgramInfo& buildInfo, std::ostream& dst)
{
	dst << "source:\n"
		<< "---\n"
		<< buildInfo.source << "\n"
		<< "---\n";
}

vk::ProgramBinary* compileProgram (const glu::ProgramSources& source, std::ostream& buildLog)
{
	glu::ShaderProgramInfo	buildInfo;

	try
	{
		return vk::buildProgram(source, vk::PROGRAM_FORMAT_SPIRV, &buildInfo);
	}
	catch (const tcu::Exception&)
	{
		writeBuildLogs(buildInfo, buildLog);
		throw;
	}
}

vk::ProgramBinary* compileProgram (const vk::SpirVAsmSource& source, std::ostream& buildLog)
{
	vk::SpirVProgramInfo	buildInfo;

	try
	{
		return vk::assembleProgram(source, &buildInfo);
	}
	catch (const tcu::Exception&)
	{
		writeBuildLogs(buildInfo, buildLog);
		throw;
	}
}

struct BuiltProgram
{
	vk::ProgramIdentifier			id;
	bool							buildOk;
	UniquePtr<vk::ProgramBinary>	binary;		// Null if build failed
	std::string						buildLog;

	BuiltProgram (const vk::ProgramIdentifier&	id_,
				  bool							buildOk_,
				  MovePtr<vk::ProgramBinary>	binary_,
				  const std::string&			buildLog_)
		: id		(id_)
		, buildOk	(buildOk_)
		, binary	(binary_)
		, buildLog	(buildLog_)
	{
	}
};

typedef SharedPtr<BuiltProgram> BuiltProgramSp;

template<typename IteratorType>
BuiltProgramSp buildProgram (IteratorType progIter, const std::string& casePath)
{
	std::ostringstream			buildLog;
	MovePtr<vk::ProgramBinary>	programBinary;
	bool						buildOk			= false;

	try
	{
		programBinary	= MovePtr<vk::ProgramBinary>(compileProgram(progIter.getProgram(), buildLog));
		buildOk			= true;
	}
	catch (const std::exception&)
	{
		// Ignore, buildOk = false
		DE_ASSERT(!programBinary);
	}

	return BuiltProgramSp(new BuiltProgram(vk::ProgramIdentifier(casePath, progIter.getName()),
										   buildOk,
										   programBinary,
										   buildLog.str()));
}

} // anonymous

BuildStats buildPrograms (tcu::TestContext& testCtx, const std::string& dstPath, bool validateBinaries)
{
	const UniquePtr<tcu::TestPackageRoot>	root		(createRoot(testCtx));
	tcu::DefaultHierarchyInflater			inflater	(testCtx);
	tcu::TestHierarchyIterator				iterator	(*root, inflater, testCtx.getCommandLine());
	const tcu::DirArchive					srcArchive	(dstPath.c_str());
	UniquePtr<vk::BinaryRegistryWriter>		writer		(new vk::BinaryRegistryWriter(dstPath));
	BuildStats								stats;

	while (iterator.getState() != tcu::TestHierarchyIterator::STATE_FINISHED)
	{
		if (iterator.getState() == tcu::TestHierarchyIterator::STATE_ENTER_NODE &&
			tcu::isTestNodeTypeExecutable(iterator.getNode()->getNodeType()))
		{
			const TestCase* const		testCase	= dynamic_cast<TestCase*>(iterator.getNode());
			const string				casePath	= iterator.getNodePath();
			vk::SourceCollections		sourcePrograms;
			vector<BuiltProgramSp>		builtPrograms;

			tcu::print("%s\n", casePath.c_str());

			testCase->initPrograms(sourcePrograms);

			for (vk::GlslSourceCollection::Iterator progIter = sourcePrograms.glslSources.begin();
				 progIter != sourcePrograms.glslSources.end();
				 ++progIter)
			{
				builtPrograms.push_back(buildProgram(progIter, casePath));
			}

			for (vk::SpirVAsmCollection::Iterator progIter = sourcePrograms.spirvAsmSources.begin();
				 progIter != sourcePrograms.spirvAsmSources.end();
				 ++progIter)
			{
				builtPrograms.push_back(buildProgram(progIter, casePath));
			}

			// Process programs
			for (vector<BuiltProgramSp>::const_iterator progIter = builtPrograms.begin();
				 progIter != builtPrograms.end();
				 ++progIter)
			{
				const BuiltProgram&	program	= **progIter;

				if (program.buildOk)
				{
					std::ostringstream	validationLog;

					writer->storeProgram(program.id, *program.binary);

					if (validateBinaries &&
						!vk::validateProgram(*program.binary, &validationLog))
					{
						tcu::print("ERROR: validation failed for %s\n", program.id.programName.c_str());
						tcu::print("%s\n", validationLog.str().c_str());
						stats.numFailed += 1;
					}
					else
						stats.numSucceeded += 1;
				}
				else
				{
					tcu::print("ERROR: failed to build %s\n", program.id.programName.c_str());
					tcu::print("%s\n", program.buildLog.c_str());
					stats.numFailed += 1;
				}
			}
		}

		iterator.next();
	}

	writer->writeIndex();

	return stats;
}

} // vkt

namespace opt
{

DE_DECLARE_COMMAND_LINE_OPT(DstPath,	std::string);
DE_DECLARE_COMMAND_LINE_OPT(Cases,		std::string);
DE_DECLARE_COMMAND_LINE_OPT(Validate,	bool);

} // opt

void registerOptions (de::cmdline::Parser& parser)
{
	using de::cmdline::Option;

	parser << Option<opt::DstPath>	("d", "dst-path",		"Destination path",	"out")
		   << Option<opt::Cases>	("n", "deqp-case",		"Case path filter (works as in test binaries)")
		   << Option<opt::Validate>	("v", "validate-spv",	"Validate generated SPIR-V binaries");
}

int main (int argc, const char* argv[])
{
	de::cmdline::CommandLine	cmdLine;
	tcu::CommandLine			deqpCmdLine;

	{
		de::cmdline::Parser		parser;
		registerOptions(parser);
		if (!parser.parse(argc, argv, &cmdLine, std::cerr))
		{
			parser.help(std::cout);
			return -1;
		}
	}

	{
		vector<const char*> deqpArgv;

		deqpArgv.push_back("unused");

		if (cmdLine.hasOption<opt::Cases>())
		{
			deqpArgv.push_back("--deqp-case");
			deqpArgv.push_back(cmdLine.getOption<opt::Cases>().c_str());
		}

		if (!deqpCmdLine.parse((int)deqpArgv.size(), &deqpArgv[0]))
			return -1;
	}

	try
	{
		tcu::DirArchive			archive			(".");
		tcu::TestLog			log				(deqpCmdLine.getLogFileName(), deqpCmdLine.getLogFlags());
		tcu::Platform			platform;
		tcu::TestContext		testCtx			(platform, archive, log, deqpCmdLine, DE_NULL);

		const vkt::BuildStats	stats			= vkt::buildPrograms(testCtx,
																	 cmdLine.getOption<opt::DstPath>(),
																	 cmdLine.getOption<opt::Validate>());

		tcu::print("DONE: %d passed, %d failed\n", stats.numSucceeded, stats.numFailed);

		return stats.numFailed == 0 ? 0 : -1;
	}
	catch (const std::exception& e)
	{
		tcu::die("%s", e.what());
	}
}
<commit_msg>Parallelize vk-build-programs<commit_after>/*-------------------------------------------------------------------------
 * Vulkan Conformance Tests
 * ------------------------
 *
 * Copyright (c) 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Utility for pre-compiling source programs to SPIR-V
 *//*--------------------------------------------------------------------*/

#include "tcuDefs.hpp"
#include "tcuCommandLine.hpp"
#include "tcuPlatform.hpp"
#include "tcuResource.hpp"
#include "tcuTestLog.hpp"
#include "tcuTestHierarchyIterator.hpp"
#include "deUniquePtr.hpp"
#include "vkPrograms.hpp"
#include "vkBinaryRegistry.hpp"
#include "vktTestCase.hpp"
#include "vktTestPackage.hpp"
#include "deUniquePtr.hpp"
#include "deCommandLine.hpp"
#include "deSharedPtr.hpp"
#include "deThread.hpp"
#include "deThreadSafeRingBuffer.hpp"
#include "dePoolArray.hpp"

#include <iostream>

using std::vector;
using std::string;
using de::UniquePtr;
using de::MovePtr;
using de::SharedPtr;

namespace vkt
{

namespace // anonymous
{

typedef de::SharedPtr<glu::ProgramSources>	ProgramSourcesSp;
typedef de::SharedPtr<vk::SpirVAsmSource>	SpirVAsmSourceSp;
typedef de::SharedPtr<vk::ProgramBinary>	ProgramBinarySp;

class Task
{
public:
	virtual void	execute		(void) = 0;
};

typedef de::ThreadSafeRingBuffer<Task*>	TaskQueue;

class TaskExecutorThread : public de::Thread
{
public:
	TaskExecutorThread (TaskQueue& tasks)
		: m_tasks(tasks)
	{
		start();
	}

	void run (void)
	{
		for (;;)
		{
			Task* const	task	= m_tasks.popBack();

			if (task)
				task->execute();
			else
				break; // End of tasks - time to terminate
		}
	}

private:
	TaskQueue&	m_tasks;
};

class TaskExecutor
{
public:
								TaskExecutor		(deUint32 numThreads);
								~TaskExecutor		(void);

	void						submit				(Task* task);
	void						waitForComplete		(void);

private:
	typedef de::SharedPtr<TaskExecutorThread>	ExecThreadSp;

	std::vector<ExecThreadSp>	m_threads;
	TaskQueue					m_tasks;
};

TaskExecutor::TaskExecutor (deUint32 numThreads)
	: m_threads	(numThreads)
	, m_tasks	(m_threads.size() * 1024u)
{
	for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
		m_threads[ndx] = ExecThreadSp(new TaskExecutorThread(m_tasks));
}

TaskExecutor::~TaskExecutor (void)
{
	for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
		m_tasks.pushFront(DE_NULL);

	for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
		m_threads[ndx]->join();
}

void TaskExecutor::submit (Task* task)
{
	DE_ASSERT(task);
	m_tasks.pushFront(task);
}

class SyncTask : public Task
{
public:
	SyncTask (de::Semaphore* enterBarrier, de::Semaphore* inBarrier, de::Semaphore* leaveBarrier)
		: m_enterBarrier	(enterBarrier)
		, m_inBarrier		(inBarrier)
		, m_leaveBarrier	(leaveBarrier)
	{}

	SyncTask (void)
		: m_enterBarrier	(DE_NULL)
		, m_inBarrier		(DE_NULL)
		, m_leaveBarrier	(DE_NULL)
	{}

	void execute (void)
	{
		m_enterBarrier->increment();
		m_inBarrier->decrement();
		m_leaveBarrier->increment();
	}

private:
	de::Semaphore*	m_enterBarrier;
	de::Semaphore*	m_inBarrier;
	de::Semaphore*	m_leaveBarrier;
};

void TaskExecutor::waitForComplete (void)
{
	de::Semaphore			enterBarrier	(0);
	de::Semaphore			inBarrier		(0);
	de::Semaphore			leaveBarrier	(0);
	std::vector<SyncTask>	syncTasks		(m_threads.size());

	for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
	{
		syncTasks[ndx] = SyncTask(&enterBarrier, &inBarrier, &leaveBarrier);
		submit(&syncTasks[ndx]);
	}

	for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
		enterBarrier.decrement();

	for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
		inBarrier.increment();

	for (size_t ndx = 0; ndx < m_threads.size(); ++ndx)
		leaveBarrier.decrement();
}

struct Program
{
	enum Status
	{
		STATUS_NOT_COMPLETED = 0,
		STATUS_FAILED,
		STATUS_PASSED,

		STATUS_LAST
	};

	vk::ProgramIdentifier	id;

	Status					buildStatus;
	std::string				buildLog;
	ProgramBinarySp			binary;

	Status					validationStatus;
	std::string				validationLog;

	explicit				Program		(const vk::ProgramIdentifier& id_)
								: id				(id_)
								, buildStatus		(STATUS_NOT_COMPLETED)
								, validationStatus	(STATUS_NOT_COMPLETED)
							{}
							Program		(void)
								: id				("", "")
								, buildStatus		(STATUS_NOT_COMPLETED)
								, validationStatus	(STATUS_NOT_COMPLETED)
							{}
};

void writeBuildLogs (const glu::ShaderProgramInfo& buildInfo, std::ostream& dst)
{
	for (size_t shaderNdx = 0; shaderNdx < buildInfo.shaders.size(); shaderNdx++)
	{
		const glu::ShaderInfo&	shaderInfo	= buildInfo.shaders[shaderNdx];
		const char* const		shaderName	= getShaderTypeName(shaderInfo.type);

		dst << shaderName << " source:\n"
			<< "---\n"
			<< shaderInfo.source << "\n"
			<< "---\n"
			<< shaderName << " compile log:\n"
			<< "---\n"
			<< shaderInfo.infoLog << "\n"
			<< "---\n";
	}

	dst << "link log:\n"
		<< "---\n"
		<< buildInfo.program.infoLog << "\n"
		<< "---\n";
}

class BuildGlslTask : public Task
{
public:

	BuildGlslTask (const glu::ProgramSources& source, Program* program)
		: m_source	(source)
		, m_program	(program)
	{}

	BuildGlslTask (void) : m_program(DE_NULL) {}

	void execute (void)
	{
		glu::ShaderProgramInfo buildInfo;

		try
		{
			m_program->binary		= ProgramBinarySp(vk::buildProgram(m_source, vk::PROGRAM_FORMAT_SPIRV, &buildInfo));
			m_program->buildStatus	= Program::STATUS_PASSED;
		}
		catch (const tcu::Exception&)
		{
			std::ostringstream log;

			writeBuildLogs(buildInfo, log);

			m_program->buildStatus	= Program::STATUS_FAILED;
			m_program->buildLog		= log.str();

		}
	}

private:
	glu::ProgramSources	m_source;
	Program*			m_program;
};

void writeBuildLogs (const vk::SpirVProgramInfo& buildInfo, std::ostream& dst)
{
	dst << "source:\n"
		<< "---\n"
		<< buildInfo.source << "\n"
		<< "---\n";
}

class BuildSpirVAsmTask : public Task
{
public:
	BuildSpirVAsmTask (const vk::SpirVAsmSource& source, Program* program)
		: m_source	(source)
		, m_program	(program)
	{}

	BuildSpirVAsmTask (void) : m_program(DE_NULL) {}

	void execute (void)
	{
		vk::SpirVProgramInfo buildInfo;

		try
		{
			m_program->binary		= ProgramBinarySp(vk::assembleProgram(m_source, &buildInfo));
			m_program->buildStatus	= Program::STATUS_PASSED;
		}
		catch (const tcu::Exception&)
		{
			std::ostringstream log;

			writeBuildLogs(buildInfo, log);

			m_program->buildStatus	= Program::STATUS_FAILED;
			m_program->buildLog		= log.str();
		}
	}

private:
	vk::SpirVAsmSource	m_source;
	Program*			m_program;
};

class ValidateBinaryTask : public Task
{
public:
	ValidateBinaryTask (Program* program)
		: m_program(program)
	{}

	void execute (void)
	{
		DE_ASSERT(m_program->buildStatus == Program::STATUS_PASSED);

		std::ostringstream validationLog;

		if (vk::validateProgram(*m_program->binary, &validationLog))
			m_program->validationStatus = Program::STATUS_PASSED;
		else
			m_program->validationStatus = Program::STATUS_FAILED;
	}

private:
	Program*	m_program;
};

tcu::TestPackageRoot* createRoot (tcu::TestContext& testCtx)
{
	vector<tcu::TestNode*>	children;
	children.push_back(new TestPackage(testCtx));
	return new tcu::TestPackageRoot(testCtx, children);
}

} // anonymous

struct BuildStats
{
	int		numSucceeded;
	int		numFailed;

	BuildStats (void)
		: numSucceeded	(0)
		, numFailed		(0)
	{
	}
};

BuildStats buildPrograms (tcu::TestContext& testCtx, const std::string& dstPath, bool validateBinaries)
{
	const deUint32						numThreads			= deGetNumAvailableLogicalCores();

	TaskExecutor						executor			(numThreads);

	// de::PoolArray<> is faster to build than std::vector
	de::MemPool							programPool;
	de::PoolArray<Program>				programs			(&programPool);

	{
		// \todo [2016-09-30 pyry] Use main executor when glslang no longer requires global lock
		TaskExecutor						buildGlslExecutor	(1);
		de::MemPool							tmpPool;
		de::PoolArray<BuildGlslTask>		buildGlslTasks		(&tmpPool);
		de::PoolArray<BuildSpirVAsmTask>	buildSpirvAsmTasks	(&tmpPool);

		// Collect build tasks
		{
			const UniquePtr<tcu::TestPackageRoot>	root		(createRoot(testCtx));
			tcu::DefaultHierarchyInflater			inflater	(testCtx);
			tcu::TestHierarchyIterator				iterator	(*root, inflater, testCtx.getCommandLine());

			while (iterator.getState() != tcu::TestHierarchyIterator::STATE_FINISHED)
			{
				if (iterator.getState() == tcu::TestHierarchyIterator::STATE_ENTER_NODE &&
					tcu::isTestNodeTypeExecutable(iterator.getNode()->getNodeType()))
				{
					const TestCase* const		testCase	= dynamic_cast<TestCase*>(iterator.getNode());
					const string				casePath	= iterator.getNodePath();
					vk::SourceCollections		sourcePrograms;

					testCase->initPrograms(sourcePrograms);

					for (vk::GlslSourceCollection::Iterator progIter = sourcePrograms.glslSources.begin();
						 progIter != sourcePrograms.glslSources.end();
						 ++progIter)
					{
						programs.pushBack(Program(vk::ProgramIdentifier(casePath, progIter.getName())));
						buildGlslTasks.pushBack(BuildGlslTask(progIter.getProgram(), &programs.back()));
						buildGlslExecutor.submit(&buildGlslTasks.back());
					}

					for (vk::SpirVAsmCollection::Iterator progIter = sourcePrograms.spirvAsmSources.begin();
						 progIter != sourcePrograms.spirvAsmSources.end();
						 ++progIter)
					{
						programs.pushBack(Program(vk::ProgramIdentifier(casePath, progIter.getName())));
						buildSpirvAsmTasks.pushBack(BuildSpirVAsmTask(progIter.getProgram(), &programs.back()));
						executor.submit(&buildSpirvAsmTasks.back());
					}
				}

				iterator.next();
			}
		}

		// Need to wait until tasks completed before freeing task memory
		buildGlslExecutor.waitForComplete();
		executor.waitForComplete();
	}

	if (validateBinaries)
	{
		std::vector<ValidateBinaryTask>	validationTasks;

		validationTasks.reserve(programs.size());

		for (de::PoolArray<Program>::iterator progIter = programs.begin(); progIter != programs.end(); ++progIter)
		{
			if (progIter->buildStatus == Program::STATUS_PASSED)
			{
				validationTasks.push_back(ValidateBinaryTask(&*progIter));
				executor.submit(&validationTasks.back());
			}
		}

		executor.waitForComplete();
	}

	{
		vk::BinaryRegistryWriter	registryWriter		(dstPath);

		for (de::PoolArray<Program>::iterator progIter = programs.begin(); progIter != programs.end(); ++progIter)
		{
			if (progIter->buildStatus == Program::STATUS_PASSED)
				registryWriter.storeProgram(progIter->id, *progIter->binary);
		}

		registryWriter.writeIndex();
	}

	{
		BuildStats	stats;

		for (de::PoolArray<Program>::iterator progIter = programs.begin(); progIter != programs.end(); ++progIter)
		{
			const bool	buildOk			= progIter->buildStatus == Program::STATUS_PASSED;
			const bool	validationOk	= progIter->validationStatus != Program::STATUS_FAILED;

			if (buildOk && validationOk)
				stats.numSucceeded += 1;
			else
			{
				stats.numFailed += 1;
				tcu::print("ERROR: %s / %s: %s failed\n",
						   progIter->id.testCasePath.c_str(),
						   progIter->id.programName.c_str(),
						   (buildOk ? "validation" : "build"));
				tcu::print("%s\n", (buildOk ? progIter->validationLog.c_str() : progIter->buildLog.c_str()));
			}
		}

		return stats;
	}
}

} // vkt

namespace opt
{

DE_DECLARE_COMMAND_LINE_OPT(DstPath,	std::string);
DE_DECLARE_COMMAND_LINE_OPT(Cases,		std::string);
DE_DECLARE_COMMAND_LINE_OPT(Validate,	bool);

} // opt

void registerOptions (de::cmdline::Parser& parser)
{
	using de::cmdline::Option;

	parser << Option<opt::DstPath>	("d", "dst-path",		"Destination path",	"out")
		   << Option<opt::Cases>	("n", "deqp-case",		"Case path filter (works as in test binaries)")
		   << Option<opt::Validate>	("v", "validate-spv",	"Validate generated SPIR-V binaries");
}

int main (int argc, const char* argv[])
{
	de::cmdline::CommandLine	cmdLine;
	tcu::CommandLine			deqpCmdLine;

	{
		de::cmdline::Parser		parser;
		registerOptions(parser);
		if (!parser.parse(argc, argv, &cmdLine, std::cerr))
		{
			parser.help(std::cout);
			return -1;
		}
	}

	{
		vector<const char*> deqpArgv;

		deqpArgv.push_back("unused");

		if (cmdLine.hasOption<opt::Cases>())
		{
			deqpArgv.push_back("--deqp-case");
			deqpArgv.push_back(cmdLine.getOption<opt::Cases>().c_str());
		}

		if (!deqpCmdLine.parse((int)deqpArgv.size(), &deqpArgv[0]))
			return -1;
	}

	try
	{
		tcu::DirArchive			archive			(".");
		tcu::TestLog			log				(deqpCmdLine.getLogFileName(), deqpCmdLine.getLogFlags());
		tcu::Platform			platform;
		tcu::TestContext		testCtx			(platform, archive, log, deqpCmdLine, DE_NULL);

		const vkt::BuildStats	stats			= vkt::buildPrograms(testCtx,
																	 cmdLine.getOption<opt::DstPath>(),
																	 cmdLine.getOption<opt::Validate>());

		tcu::print("DONE: %d passed, %d failed\n", stats.numSucceeded, stats.numFailed);

		return stats.numFailed == 0 ? 0 : -1;
	}
	catch (const std::exception& e)
	{
		tcu::die("%s", e.what());
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2008 The Regents of The University of Michigan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Gabe Black
 */

#include "arch/x86/cpuid.hh"
#include "base/bitfield.hh"
#include "cpu/thread_context.hh"

namespace X86ISA {
    enum StandardCpuidFunction {
        VendorAndLargestStdFunc,
        FamilyModelStepping,
        NumStandardCpuidFuncs
    };

    enum ExtendedCpuidFunctions {
        VendorAndLargestExtFunc,
        FamilyModelSteppingBrandFeatures,
        NameString1,
        NameString2,
        NameString3,
        L1CacheAndTLB,
        L2L3CacheAndL2TLB,
        APMInfo,

        /*
         * The following are defined by the spec but not yet implemented
         */
/*      LongModeAddressSize,
        // Function 9 is reserved
        SVMInfo = 10,
        // Functions 11-24 are reserved
        TLB1GBPageInfo = 25,
        PerformanceInfo,*/

        NumExtendedCpuidFuncs
    };

    static const int vendorStringSize = 13;
    static const char vendorString[vendorStringSize] = "M5 Simulator";
    static const int nameStringSize = 48;
    static const char nameString[nameStringSize] = "Fake M5 x86_64 CPU";

    uint64_t
    stringToRegister(const char *str)
    {
        uint64_t reg = 0;
        for (int pos = 3; pos >=0; pos--) {
            reg <<= 8;
            reg |= str[pos];
        }
        return reg;
    }

    bool
    doCpuid(ThreadContext * tc, uint32_t function,
            uint32_t index, CpuidResult &result)
    {
        uint16_t family = bits(function, 31, 16);
        uint16_t funcNum = bits(function, 15, 0);
        if (family == 0x8000) {
            // The extended functions
            switch (funcNum) {
              case VendorAndLargestExtFunc:
                assert(vendorStringSize >= 12);
                result = CpuidResult(
                        0x80000000 + NumExtendedCpuidFuncs - 1,
                        stringToRegister(vendorString),
                        stringToRegister(vendorString + 4),
                        stringToRegister(vendorString + 8));
                break;
              case FamilyModelSteppingBrandFeatures:
                result = CpuidResult(0x00020f51, 0x00000405,
                                     0xe3d3fbff, 0x00000001);
                break;
              case NameString1:
              case NameString2:
              case NameString3:
                {
                    // Zero fill anything beyond the end of the string. This
                    // should go away once the string is a vetted parameter.
                    char cleanName[nameStringSize];
                    memset(cleanName, '\0', nameStringSize);
                    strncpy(cleanName, nameString, nameStringSize);

                    int offset = (funcNum - NameString1) * 16;
                    assert(nameStringSize >= offset + 16);
                    result = CpuidResult(
                            stringToRegister(cleanName + offset + 0),
                            stringToRegister(cleanName + offset + 4),
                            stringToRegister(cleanName + offset + 12),
                            stringToRegister(cleanName + offset + 8));
                }
                break;
              case L1CacheAndTLB:
                result = CpuidResult(0xff08ff08, 0xff20ff20,
                                     0x40020140, 0x40020140);
                break;
              case L2L3CacheAndL2TLB:
                result = CpuidResult(0x00000000, 0x42004200,
                                     0x00000000, 0x04008140);
                break;
              case APMInfo:
                result = CpuidResult(0x80000018, 0x68747541,
                                     0x69746e65, 0x444d4163);
                break;
/*            case LongModeAddressSize:
              case SVMInfo:
              case TLB1GBPageInfo:
              case PerformanceInfo:*/
              default:
                return false;
            }
        } else if(family == 0x0000) {
            // The standard functions
            switch (funcNum) {
              case VendorAndLargestStdFunc:
                assert(vendorStringSize >= 12);
                result = CpuidResult(
                        NumStandardCpuidFuncs - 1,
                        stringToRegister(vendorString),
                        stringToRegister(vendorString + 4),
                        stringToRegister(vendorString + 8));
                break;
              case FamilyModelStepping:
                result = CpuidResult(0x00020f51, 0000000405,
                                     0xe7d3fbff, 0x00000001);
                break;
              default:
                return false;
            }
        }
        return true;
    }
} // namespace X86ISA
<commit_msg>X86 CPUID: Return false if unknown processor family<commit_after>/*
 * Copyright (c) 2008 The Regents of The University of Michigan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Gabe Black
 */

#include "arch/x86/cpuid.hh"
#include "base/bitfield.hh"
#include "cpu/thread_context.hh"

namespace X86ISA {
    enum StandardCpuidFunction {
        VendorAndLargestStdFunc,
        FamilyModelStepping,
        NumStandardCpuidFuncs
    };

    enum ExtendedCpuidFunctions {
        VendorAndLargestExtFunc,
        FamilyModelSteppingBrandFeatures,
        NameString1,
        NameString2,
        NameString3,
        L1CacheAndTLB,
        L2L3CacheAndL2TLB,
        APMInfo,

        /*
         * The following are defined by the spec but not yet implemented
         */
/*      LongModeAddressSize,
        // Function 9 is reserved
        SVMInfo = 10,
        // Functions 11-24 are reserved
        TLB1GBPageInfo = 25,
        PerformanceInfo,*/

        NumExtendedCpuidFuncs
    };

    static const int vendorStringSize = 13;
    static const char vendorString[vendorStringSize] = "M5 Simulator";
    static const int nameStringSize = 48;
    static const char nameString[nameStringSize] = "Fake M5 x86_64 CPU";

    uint64_t
    stringToRegister(const char *str)
    {
        uint64_t reg = 0;
        for (int pos = 3; pos >=0; pos--) {
            reg <<= 8;
            reg |= str[pos];
        }
        return reg;
    }

    bool
    doCpuid(ThreadContext * tc, uint32_t function,
            uint32_t index, CpuidResult &result)
    {
        uint16_t family = bits(function, 31, 16);
        uint16_t funcNum = bits(function, 15, 0);
        if (family == 0x8000) {
            // The extended functions
            switch (funcNum) {
              case VendorAndLargestExtFunc:
                assert(vendorStringSize >= 12);
                result = CpuidResult(
                        0x80000000 + NumExtendedCpuidFuncs - 1,
                        stringToRegister(vendorString),
                        stringToRegister(vendorString + 4),
                        stringToRegister(vendorString + 8));
                break;
              case FamilyModelSteppingBrandFeatures:
                result = CpuidResult(0x00020f51, 0x00000405,
                                     0xe3d3fbff, 0x00000001);
                break;
              case NameString1:
              case NameString2:
              case NameString3:
                {
                    // Zero fill anything beyond the end of the string. This
                    // should go away once the string is a vetted parameter.
                    char cleanName[nameStringSize];
                    memset(cleanName, '\0', nameStringSize);
                    strncpy(cleanName, nameString, nameStringSize);

                    int offset = (funcNum - NameString1) * 16;
                    assert(nameStringSize >= offset + 16);
                    result = CpuidResult(
                            stringToRegister(cleanName + offset + 0),
                            stringToRegister(cleanName + offset + 4),
                            stringToRegister(cleanName + offset + 12),
                            stringToRegister(cleanName + offset + 8));
                }
                break;
              case L1CacheAndTLB:
                result = CpuidResult(0xff08ff08, 0xff20ff20,
                                     0x40020140, 0x40020140);
                break;
              case L2L3CacheAndL2TLB:
                result = CpuidResult(0x00000000, 0x42004200,
                                     0x00000000, 0x04008140);
                break;
              case APMInfo:
                result = CpuidResult(0x80000018, 0x68747541,
                                     0x69746e65, 0x444d4163);
                break;
/*            case LongModeAddressSize:
              case SVMInfo:
              case TLB1GBPageInfo:
              case PerformanceInfo:*/
              default:
                warn("x86 cpuid: unimplemented function %u", funcNum);
                return false;
            }
        } else if(family == 0x0000) {
            // The standard functions
            switch (funcNum) {
              case VendorAndLargestStdFunc:
                assert(vendorStringSize >= 12);
                result = CpuidResult(
                        NumStandardCpuidFuncs - 1,
                        stringToRegister(vendorString),
                        stringToRegister(vendorString + 4),
                        stringToRegister(vendorString + 8));
                break;
              case FamilyModelStepping:
                result = CpuidResult(0x00020f51, 0000000405,
                                     0xe7d3fbff, 0x00000001);
                break;
              default:
                warn("x86 cpuid: unimplemented function %u", funcNum);
                return false;
            }
        } else {
            warn("x86 cpuid: unknown family %#x", family);
            return false;
        }

        return true;
    }
} // namespace X86ISA
<|endoftext|>


<commit_before>// ========================================================================== //
// This file is part of DO-CV, a basic set of libraries in C++ for computer
// vision.
//
// Copyright (C) 2014 David Ok <david.ok8@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License v. 2.0. If a copy of the MPL was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.
// ========================================================================== //

#include <gtest/gtest.h>

#include <QtTest>

#include <DO/Sara/Graphics/DerivedQObjects/OpenGLWindow.hpp>

#include "event_scheduler.hpp"

Q_DECLARE_METATYPE(DO::Sara::Event)
Q_DECLARE_METATYPE(Qt::Key)
Q_DECLARE_METATYPE(Qt::MouseButtons)

using namespace DO::Sara;

TEST(TestOpenGLWindow, test_construction)
{
  int width = 300;
  int height = 300;
  QString windowName = "OpenGL Window";
  int x = 200;
  int y = 300;

  OpenGLWindow *window = new OpenGLWindow(
    width, height,
    windowName,
    x, y
  );

  EXPECT_EQ(window->width(), width);
  EXPECT_EQ(window->height(), height);
  EXPECT_EQ(window->windowTitle(), windowName);
  EXPECT_TRUE(window->isVisible());

  delete window;
}

class TestOpenGLWindowEvents: public testing::Test
{
protected: // data members.
  OpenGLWindow *_test_window;
  EventScheduler _event_scheduler;
  QPoint _mouse_pos;
  Qt::Key _key;
  int _mouse_buttons_type_id;
  int _event_type_id;

  int _wait_ms;
  int _event_time_ms;

protected: // methods.
  TestOpenGLWindowEvents()
  {
    _mouse_buttons_type_id = qRegisterMetaType<Qt::MouseButtons>(
      "Qt::MouseButtons"
      );
    _event_type_id = qRegisterMetaType<Event>("Event");
    _test_window = new OpenGLWindow(300, 300);
    _event_scheduler.set_receiver(_test_window);
    _mouse_pos = QPoint(10, 10);
    _key = Qt::Key_A;

#ifdef _WIN32
    _wait_ms = 100;
    _event_time_ms = 10;
#else
    _wait_ms = 10;
    _event_time_ms = 1;
#endif
  }

  virtual ~TestOpenGLWindowEvents()
  {
    delete _test_window;
  }

  void compare_key_event(QSignalSpy& spy) const
  {
    spy.wait(2*_wait_ms);
    EXPECT_EQ(spy.count(), 1);

    auto arguments = spy.takeFirst();
    EXPECT_EQ(arguments.at(0).toInt(), static_cast<int>(_key));
  }

};

TEST_F(TestOpenGLWindowEvents, test_key_press_event)
{
  QSignalSpy spy(_test_window, SIGNAL(pressedKey(int)));
  EXPECT_TRUE(spy.isValid());

  auto event = QKeyEvent{ QEvent::KeyPress, _key, Qt::NoModifier };
  _event_scheduler.schedule_event(&event, _event_time_ms);

  compare_key_event(spy);
}

TEST_F(TestOpenGLWindowEvents, test_key_release_event)
{
  QSignalSpy spy{ _test_window, SIGNAL(releasedKey(int)) };
  EXPECT_TRUE(spy.isValid());

  auto event = QKeyEvent{ QEvent::KeyRelease, _key, Qt::NoModifier };
  _event_scheduler.schedule_event(&event, _event_time_ms);

  compare_key_event(spy);
}

TEST_F(TestOpenGLWindowEvents, test_send_no_event)
{
  QSignalSpy spy{ _test_window, SIGNAL(sendEvent(Event)) };
  EXPECT_TRUE(spy.isValid());

  QMetaObject::invokeMethod(_test_window, "waitForEvent",
                            Qt::AutoConnection, Q_ARG(int, 1));

  // Nothing happens.
  EXPECT_TRUE(spy.wait(10));
  EXPECT_EQ(spy.count(), 1);
  auto arguments = spy.takeFirst();
  auto arg = arguments.at(0);
  arg.convert(_event_type_id);
  auto event = arguments.at(0).value<Event>();
  EXPECT_EQ(event.type, DO::Sara::NO_EVENT);
}

TEST_F(TestOpenGLWindowEvents, test_send_pressed_key_event)
{
  // Spy the sendEvent signal.
  QSignalSpy spy{ _test_window, SIGNAL(sendEvent(Event)) };
  EXPECT_TRUE(spy.isValid());

  // Ask the testing window to wait.
  QMetaObject::invokeMethod(_test_window, "waitForEvent",
                            Qt::AutoConnection, Q_ARG(int, _wait_ms));

  // Schedule a key press event.
  auto qt_event = QKeyEvent{ QEvent::KeyPress, _key, Qt::NoModifier };
  _event_scheduler.schedule_event(&qt_event, _event_time_ms);

  // The spy waits for the events.
  EXPECT_TRUE(spy.wait(2*_wait_ms));

  // Check that the spy received one key press event.
  EXPECT_EQ(spy.count(), 1);

  // Check the details of the key press event.
  auto arguments = spy.takeFirst();
  auto arg = arguments.at(0);
  arg.convert(_event_type_id);
  auto event = arguments.at(0).value<Event>();
  EXPECT_EQ(event.type, DO::Sara::KEY_PRESSED);
  EXPECT_EQ(event.key, _key);
}

TEST_F(TestOpenGLWindowEvents, test_send_released_key_event)
{
  // Spy the sendEvent signal.
  QSignalSpy spy{ _test_window, SIGNAL(sendEvent(Event)) };
  EXPECT_TRUE(spy.isValid());

  // Ask the testing window to wait for an event.
  QMetaObject::invokeMethod(_test_window, "waitForEvent",
                            Qt::AutoConnection, Q_ARG(int, _wait_ms));

  // Schedule a key press event.
  auto qt_event = QKeyEvent{ QEvent::KeyRelease, _key, Qt::NoModifier };
  _event_scheduler.schedule_event(&qt_event, _event_time_ms);

  // Check that the spy received one key press event.
  EXPECT_TRUE(spy.wait(2*_wait_ms));
  EXPECT_EQ(spy.count(), 1);

  // Check the details of the key release event.
  auto arguments = spy.takeFirst();
  auto arg = arguments.at(0);
  arg.convert(_event_type_id);
  auto event = arguments.at(0).value<Event>();
  EXPECT_EQ(event.type, DO::Sara::KEY_RELEASED);
  EXPECT_EQ(event.key, _key);
}

int main(int argc, char *argv[])
{
  QApplication app{ argc, argv };
  app.setAttribute(Qt::AA_Use96Dpi, true);

  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
<commit_msg>MAINT: change key value.<commit_after>// ========================================================================== //
// This file is part of DO-CV, a basic set of libraries in C++ for computer
// vision.
//
// Copyright (C) 2014 David Ok <david.ok8@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License v. 2.0. If a copy of the MPL was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.
// ========================================================================== //

#include <gtest/gtest.h>

#include <QtTest>

#include <DO/Sara/Graphics/DerivedQObjects/OpenGLWindow.hpp>

#include "event_scheduler.hpp"

Q_DECLARE_METATYPE(DO::Sara::Event)
Q_DECLARE_METATYPE(Qt::Key)
Q_DECLARE_METATYPE(Qt::MouseButtons)

using namespace DO::Sara;

TEST(TestOpenGLWindow, test_construction)
{
  int width = 300;
  int height = 300;
  QString windowName = "OpenGL Window";
  int x = 200;
  int y = 300;

  OpenGLWindow *window = new OpenGLWindow(
    width, height,
    windowName,
    x, y
  );

  EXPECT_EQ(window->width(), width);
  EXPECT_EQ(window->height(), height);
  EXPECT_EQ(window->windowTitle(), windowName);
  EXPECT_TRUE(window->isVisible());

  delete window;
}

class TestOpenGLWindowEvents: public testing::Test
{
protected: // data members.
  OpenGLWindow *_test_window;
  EventScheduler _event_scheduler;
  QPoint _mouse_pos;
  Qt::Key _key;
  int _mouse_buttons_type_id;
  int _event_type_id;

  int _wait_ms;
  int _event_time_ms;

protected: // methods.
  TestOpenGLWindowEvents()
  {
    _mouse_buttons_type_id = qRegisterMetaType<Qt::MouseButtons>(
      "Qt::MouseButtons"
      );
    _event_type_id = qRegisterMetaType<Event>("Event");
    _test_window = new OpenGLWindow(300, 300);
    _event_scheduler.set_receiver(_test_window);
    _mouse_pos = QPoint(10, 10);
    _key = Qt::Key_F;

#ifdef _WIN32
    _wait_ms = 100;
    _event_time_ms = 10;
#else
    _wait_ms = 10;
    _event_time_ms = 1;
#endif
  }

  virtual ~TestOpenGLWindowEvents()
  {
    delete _test_window;
  }

  void compare_key_event(QSignalSpy& spy) const
  {
    spy.wait(2*_wait_ms);
    EXPECT_EQ(spy.count(), 1);

    auto arguments = spy.takeFirst();
    EXPECT_EQ(arguments.at(0).toInt(), static_cast<int>(_key));
  }

};

TEST_F(TestOpenGLWindowEvents, test_key_press_event)
{
  QSignalSpy spy(_test_window, SIGNAL(pressedKey(int)));
  EXPECT_TRUE(spy.isValid());

  auto event = QKeyEvent{ QEvent::KeyPress, _key, Qt::NoModifier };
  _event_scheduler.schedule_event(&event, _event_time_ms);

  compare_key_event(spy);
}

TEST_F(TestOpenGLWindowEvents, test_key_release_event)
{
  QSignalSpy spy{ _test_window, SIGNAL(releasedKey(int)) };
  EXPECT_TRUE(spy.isValid());

  auto event = QKeyEvent{ QEvent::KeyRelease, _key, Qt::NoModifier };
  _event_scheduler.schedule_event(&event, _event_time_ms);

  compare_key_event(spy);
}

TEST_F(TestOpenGLWindowEvents, test_send_no_event)
{
  QSignalSpy spy{ _test_window, SIGNAL(sendEvent(Event)) };
  EXPECT_TRUE(spy.isValid());

  QMetaObject::invokeMethod(_test_window, "waitForEvent",
                            Qt::AutoConnection, Q_ARG(int, 1));

  // Nothing happens.
  EXPECT_TRUE(spy.wait(10));
  EXPECT_EQ(spy.count(), 1);
  auto arguments = spy.takeFirst();
  auto arg = arguments.at(0);
  arg.convert(_event_type_id);
  auto event = arguments.at(0).value<Event>();
  EXPECT_EQ(event.type, DO::Sara::NO_EVENT);
}

TEST_F(TestOpenGLWindowEvents, test_send_pressed_key_event)
{
  // Spy the sendEvent signal.
  QSignalSpy spy{ _test_window, SIGNAL(sendEvent(Event)) };
  EXPECT_TRUE(spy.isValid());

  // Ask the testing window to wait.
  QMetaObject::invokeMethod(_test_window, "waitForEvent",
                            Qt::AutoConnection, Q_ARG(int, _wait_ms));

  // Schedule a key press event.
  auto qt_event = QKeyEvent{ QEvent::KeyPress, _key, Qt::NoModifier };
  _event_scheduler.schedule_event(&qt_event, _event_time_ms);

  // The spy waits for the events.
  EXPECT_TRUE(spy.wait(2*_wait_ms));

  // Check that the spy received one key press event.
  EXPECT_EQ(spy.count(), 1);

  // Check the details of the key press event.
  auto arguments = spy.takeFirst();
  auto arg = arguments.at(0);
  arg.convert(_event_type_id);
  auto event = arguments.at(0).value<Event>();
  EXPECT_EQ(event.type, DO::Sara::KEY_PRESSED);
  EXPECT_EQ(event.key, _key);
}

TEST_F(TestOpenGLWindowEvents, test_send_released_key_event)
{
  // Spy the sendEvent signal.
  QSignalSpy spy{ _test_window, SIGNAL(sendEvent(Event)) };
  EXPECT_TRUE(spy.isValid());

  // Ask the testing window to wait for an event.
  QMetaObject::invokeMethod(_test_window, "waitForEvent",
                            Qt::AutoConnection, Q_ARG(int, _wait_ms));

  // Schedule a key press event.
  auto qt_event = QKeyEvent{ QEvent::KeyRelease, _key, Qt::NoModifier };
  _event_scheduler.schedule_event(&qt_event, _event_time_ms);

  // Check that the spy received one key press event.
  EXPECT_TRUE(spy.wait(2*_wait_ms));
  EXPECT_EQ(spy.count(), 1);

  // Check the details of the key release event.
  auto arguments = spy.takeFirst();
  auto arg = arguments.at(0);
  arg.convert(_event_type_id);
  auto event = arguments.at(0).value<Event>();
  EXPECT_EQ(event.type, DO::Sara::KEY_RELEASED);
  EXPECT_EQ(event.key, _key);
}

int main(int argc, char *argv[])
{
  QApplication app{ argc, argv };
  app.setAttribute(Qt::AA_Use96Dpi, true);

  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
<|endoftext|>