text:
/*!
 * \file src/relay/transforms/device_planner.cc
 * \brief Determines a unique \p VirtualDevice to hold the result of every Relay sub-expression.
 * This pass can be run multiple times, and can be run both before and after lowering.
 *
 * TODO(mbs): Rename VirtualDevice |-> VirtualDevice, and use 'virtual device' (or just 'device')
 * throughout.
 *
 * We say a Relay expression E is 'on device D' if the result of executing E is stored on D.
 * We represent D by an \p VirtualDevice, which means we can track anywhere from an arbitrary device
 * of some \p DLDeviceType to a specific memory scope on a specific (virtual) \p Device who's
 * code is compiled with a specific \p Target.
 *
 * Note that 'stored on device D' is almost but not quite the same as 'executes on device D',
 * see below.
 *
 * This pass works by collecting and solving device constraints, using defaulting heuristics to
 * resolve any remaining undetermined devices, and encoding the results on the output in a form
 * that's reasonably friendly to downstream passes.
 *
 * Specific \p VirtualDevices flow into the constraints from five places:
 *  - Existing "device_copy" CallNodes (with a \p DeviceCopyAttrs attribute) specify a
 *    'src_virtual_device' and 'dst_virtual_device' \p VirtualDevice. Those constrain the argument
 * and context of the call respectively. It is ok if source and destination devices are the same,
 * such no-op copies will be removed after accounting for the device preference.
 *  - Existing "on_device" CallNodes (with a \p OnDeviceAttrs attribute) specify an
 * 'virtual_device', which constrains the argument of the call, but (usually, see below) leaves the
 * context unconstrained. These are called 'annotations' in the rest of the code, have no
 * operational significance by themselves, but may trigger the insertion of a new "device_copy" call
 * by this pass. In two situations the result of an "on_device" CallNode may also be constrained to
 * the given 'virtual_device':
 *     - The "on_device" call occurs at the top-level of a function body, or occurs as an
 *       immediately let-bound expression. In this situation the extra degree of freedom in
 *       the function result and let-binding leads to surprising device copies, so we simply
 *       force the function result or let-bound variable to the given device.
 *     - The \p OnDeviceAttrs has an \p is_fixed field of \p true, which indicates we inserted
 *       it ourselves during an earlier invocation of this pass. This helps make this pass
 *       idempotent.
 *  - Some special operators require their arguments or results to be on the 'host' (typcially
 *    a CPU) \p VirtualDevice, see below.
 *  - Any \p PrimFuncs in the \p IRModule (if \p LowerTEPass has already run) may constrain their
 *    argument buffers to have a specific memory scope, which is part of \p VirtualDevice.
 *  - Annotations left over from a previous run of this pass, such as 'param_virtual_devices' and
 *    'result_virtual_device' function attributes we introduce below. This is so the pass is
 * idempotent and can be re-run to flow additional memory scope constraints.
 *
 * We proceed in four phases:
 *
 * Phase 0
 * -------
 * We rewrite the programs to handle some special cases:
 *  - "on_device" calls at the top-level of function or immediately let-bound are rewritten
 *    to have \code is_fixed=true \endcode.
 *  - We wish to treat \code on_device(expr, device_type=d).0 \endcode as if it were written
 *    \code on_device(expr.0, device_type_d) \endcode. I.e. we prefer to copy the projection from
 *    the tuple rather than project from a copy of the tuple. We'll do this by rewriting.
 *  - We are prepared to insert device_copies on the arguments and result of calls to PrimFuncs,
 *    on the assumption a) we already ran PlanDevices before lowering so we are not allowing
 *    any new cross-device copies, but b) after lowering we may have new memory scope constraits
 *    to deal with.
 *
 * Phase 1
 * -------
 * We flow constraints from the "on_device" and "device_copy" calls, PrimFunc buffer memory scopes,
 * and some special ops, to all other Relay sub-expressions.
 *
 * For a primitive such as \code add(e1, e2) \endcode all arguments and results must be on the
 * same device. However each call site can use a different device. In other words primitives are
 * 'device polymorphic' since we compile and execute them for each required device. ADT constructors
 * are similarly polymorphic, but require all constructor args to be on the same device.
 *
 * For most Relay expressions the device for the overall expression is the same as the device
 * for its sub-expressions. E.g. each field of a tuple must be on the same device as the tuple
 * itself, the condition and arms of an \p if must all be on the same device as the overall \p if,
 * and so on.
 *
 * Some special ops (or 'dialects') are handled:
 *  - Relay supports computing the shape of tensors and operators at runtime using "shape_of"
 *    and "reshape_tensor". Shapes must only be held on the CPU, but the tensors they describe
 *    may reside on any device.
 *  - Explicit memory allocation is done using the "alloc_storage" and "alloc_tensor". Again
 *    shapes reside on the CPU, but the allocated tensors may reside on any device.
 *
 * Two Relay expression have special handling:
 *  - For \code let x = e1; e2 \endcode the result of \p e2 must be on the same device as the
 *    overall let. However the result of \p e1 may be on a different device.
 *  - For a function \code fn(x, y) { body } \endcode the result of the function must be on the
 *    same device as \p body. However parameters \p x and \p may be on different devices, even
 *    different from each other. Every call to the function must use the same choice of parameter
 *    and result devices -- there is no 'device polymorphism' for Relay functions.
 *
 * Currently \p PrimFuncs and external functions do not carry over their parameter and result
 * devices from their original Relay Function representations. However we know all calls to those
 * functions are device-consistent, thus no information is lost.
 *
 * Phase 2
 * -------
 * After flowing constraints we apply some defaulting heuristics (using a global default \p
 * VirtualDevice) to fix the device for any as-yet unconstrained sub-expressions.
 *  - Unconstrained function result devices default to the global default device.
 *  - Unconstrained function parameters devices default to the device for the function result.
 *  - Unconstrained let-bound expression devices default to the device for the overall let.
 * TODO(mbs): These are very simple minded heuristics, and ultimately we'd like to treat the
 * assignment of the remaining unconstrained sub-expressions as an optimiziation problem in itself.
 * This requires a formal notion of 'choicepoint' inside the compiler which can integrate with
 * automation.
 *
 * Phase 3
 * -------
 * Finally, the result of this analysis is reified into the result as:
 *  - Additional "param_virtual_devices" (an \p Array<VirtualDevice>) and "result_virtual_device"
 * (an \p VirtualDevice) attributes for every function (both top-level and local). These describe
 * the devices for the function's parameters and the result.
 *  - Additional "device_copy" CallNodes where a copy is required in order to respect the
 *    intent of the original "on_device" CallNodes.
 *  - Additional "on_device" CallNodes where the device type of an expression is not trivially
 *    implied by the lexically enclosing "on_device" CallNode or function attribute. In practice
 *    this means "on_device" CallNodes may appear in two places:
 *     - On let-bound expressions. It is tempting to elide the "on_device" if the let-bound value
 *       has the same device as the overall let expression. However this would mean passes which
 *       inline let-bound values, such as FoldConstant and DeadCodeElimination, would need to us
 *       a DeviceAware visitor which in turn requires the expression to be in ANF to avoid
 *       deep recursion. To minimize disruption we always include the "on_device" so that it
 *       can follow the inline.
 *     - On a call argument if its device differs from the call result. In particular, the
 *       argument to a "device_copy" call will always be wrapped in an "on_device". (That may
 *       seem pedantic but simplifies downstream handling.)
 *    However since we make it easy to track devices for variables we never wrap an "on_device"
 *    around a var or global var. These uses of "on_device" imply both the argument and result are
 *    on the same device. We signal this by setting the 'is_fixed' OnDeviceAttrs field to true,
 *    which helps make this pass idempotent.
 *  - The buffer maps for called PrimFuncs are updated to capture memory scopes.
 *
 * Helper visitors (in device_aware_visitors.h) can be used by downstream transforms to recover
 * the device for any expression for their own use, e.g. during memory planning. All downstream
 * passes must preserve the lexical scoping of the "on_device" CallNodes. E.g. conversion
 * to ANF must respect the lexical scoping convention:
 * \code
 *   f(on_device(g(h(a, b), c), virtual_device=CPU))
 *   ==>
 *   let %x0 = on_device(h(a, b), virtual_device=CPU)
 *   let %x1 = on_device(g(%x0), virtual_device=CPU)
 *   f(on_device(%x1, virtual_device=CPU))
 * \endcode
 *
 * This pass can be run before FuseOps so that it can use device-specific fusion rules.
 *
 * 'Stored on' vs 'Executes on'
 * ----------------------------
 * Obviously for a primitive call \code add(x, y) \endcode we can execute the primitive on the
 * same device as will hold its result. Thus 'executes on' is the same as 'stored on' for
 * primitives.
 *
 * But what about for arbitrary Relay expressions? Most backends (interpreter, graph, VM) are
 * implicitly executed on the 'host' CPU, with only primitive evaluation handed off to specific
 * devices, thus the notion of 'executes on' is mute. AOT backends on the other hand need to
 * know exactly which device (possibly one of a number of available 'CPU'-like devices) is
 * responsible for execution. Currently that's handled independently by the \p AnnotateTargets
 * pass, but we'd like to fold that into device planning here to ensure everything is consistent.
 *
 * Obviously since tensors are passed-by-pointer it's quite possible to execute a Relay
 * expression (eg an \p if expression) on one device even though the tensor data resides on
 * another. But for AOT that flexibility seems excessive. So we'd like to just take 'executes on'
 * to be 'stored on' exactly. In particular, for a Relay function, we'd like to be able to just
 * compile the function body for the function's result device.
 *
 * This works after conversion to ANF provided the compilation for a let expression is prepared
 * to make a cross-device call. However we leave it to a downstream transformation to heuristically
 * minimize cross-device calls by moving device copies out of functions. E.g.:
 * \code
 *   def @f() {  // execute on CPU
 *     let x = on_device(...GPU computation..., virtual_device=GPU);
 *     device_copy(...GPU computation..., src_dev_type=GPU, dst_dev_type=CPU)
 *   }
 *   def @main() {
 *     ... call @f() on CPU ...
 *   }
 * \endcode
 * could be rewritten to:
 * \code
 *   def @f() {  // execute on GPU
 *     let x = ...GPU computation...;
 *     ...GPU computation...
 *   }
 *   def @main() {
 *     let x = device_copy(@f(), src_dev_type=GPU, dst_dev_type=CPU)
 *     ... use x on CPU ...
 *   }
 * \endcode
 *
 * Higher-order shenanigans
 * ------------------------
 * Relay is a 'mostly' higher-order language -- we can let-bind functions, pass functions
 * as arguments (even anonymous functions), return functions, evaluate conditional expressions
 * over functions, and so on. We handle this during constraint solving using the domain:
 * \code
 *   D  ::= <specific device type>   -- first-order
 *        | fn(D,...,D):D            -- higher-order
 * \endcode
 * In this way we can determine the device for all function parameters and results. E.g. for
 * \code
 *   let f = fn(x, y) { ... }
 *   let g = fn(f, z) { f(z, z) }
 *   g(f, on_device(..., virtual_device=CPU))
 * \endcode
 * the parameters \p x and \p y will be on the CPU.
 *
 * But now look closely at the call \code e1(e2, e3) \endcode. We know \p e1 must evaluate to a
 * function. Our analysis must guarantee that the function's parameters and result devices are
 * consistent for \p e2, \p e3, and the context of the call. But:
 *  - Which device holds the closure result of evaluating \p e1 ?
 *  - If \p e2 is of function type, what does that mean when we say every function parameter
 *    is on a device?
 *  - If \p e1 returns a function, what does that mean when we say every function result is
 *    on a device?
 *
 * Since higher-order aspects are later compiled away (by 'defunctionalization'
 * aka 'firstification') we'd prefer not to have to answer any of those questions. In particular,
 * we really don't want our domain \p D to allow for yet another device for the function closure.
 * So we'll just force the 'device for a function' to be the same as the device for the function's
 * result using the notion of the 'result domain' for a domain:
 * \code
 *   result_domain(<specific device type>) = <specific device type>
 *   result_domain(fn(D1,...,Dn):Dr)       = result_domain(Dr)
 * \endcode
 *
 * Similarly the domain does not have entries for tuples, references, or ADTs. Whenever the
 * analysis encounters a function inside one of those it simply forces all argument and result
 * devices for the function to match the device for the first-order expression. For example,
 * if the tuple \code (fn(x, y) { ... }, 3) \endcode is on the GPU then the inner function
 * parameters and result must similarly be on the GPU.
 *
 * -------
 * | AOR |  This pass supports all of Relay.
 * -------
 *    ^
 *    |
 *    `-- Mark's stamp of completeness :-)
 *
 * TODO(mbs): Proper diagnostics for unification failure using spans.
 * TODO(mbs): We may want some 'device polymorphism' for Relay functions. Eg it's ok for the
 * function to be called with params/result on different (virtual) device ids provided the target
 * and memory scopes are consistent.
 */

#include <tvm/ir/transform.h>
#include <tvm/relay/analysis.h>
#include <tvm/relay/attrs/annotation.h>
#include <tvm/relay/attrs/device_copy.h>
#include <tvm/relay/attrs/memory.h>
#include <tvm/relay/expr_functor.h>
#include <tvm/relay/op.h>
#include <tvm/relay/pattern_functor.h>
#include <tvm/relay/transform.h>
#include <tvm/relay/type.h>
#include <tvm/runtime/c_runtime_api.h>
#include <tvm/runtime/object.h>
#include <tvm/tir/function.h>
#include <tvm/tir/stmt_functor.h>

#include <unordered_map>

#include "../../tir/analysis/device_constraint_utils.h"
#include "../op/annotation/annotation.h"
#include "../op/memory/device_copy.h"
#include "../op/memory/on_device.h"
#include "./device_domains.h"

namespace tvm {
namespace relay {
namespace transform {

namespace {

/* =============== Phase 0 =============== */

/*!
 * \brief Rewrites "on_device" calls to handle some special cases.
 *
 *  - Don't let the device for %x remain unconstrained:
 *    \code
 *      let %x = on_device(e, virtual_device=d)
 *      ==> let %x = on_device(e, virtual_device=d, constraint=kBoth)
 *    \endcode
 *
 *  - Don't let the function result remain unconstrained:
 *    \code
 *      fn(%x) { on_device(e, virtual_device=d) }
 *      ==> fn(%x) { on_device(e, virtual_device=d, constraint=kBoth)
 *    \endcode
 *
 *  - Project-then-copy rather than copy-then-project:
 *    \code
 *      on_device(e).0
 *      ==> on_device(e.0)
 *    \endcode
 *
 *  - Be prepared to copy arguments and results on primitive call boundaries in case memory
 *    scopes don't line up. We'll use the 'fully unconstrained' version of on_device so that
 *    we can allow for a device_copy without knowing the specific device for the arguments.
 *    \code
 *      call_lowered(@prim, (a, b))
 *      ==> copy_ok(call_lowered(@prim, (copy_ok(a), copy_ok(b))))
 *      where
 *        copy_ok(x) = on_device(x, virtual_device=VirtualDevice::FullyUnconstrained,
 *                               constrain_body=False, constrain_result=False)
 *    \endcode
 */
class RewriteOnDevices : public ExprMutator {
 public:
  explicit RewriteOnDevices(IRModule mod) : mod_(std::move(mod)) {}

 private:
  Expr VisitExpr_(const TupleGetItemNode* tuple_get_item_node) final {
    Expr tuple = VisitExpr(tuple_get_item_node->tuple);
    OnDeviceProps props = GetOnDeviceProps(tuple);

    Expr tuple_get_item = WithFields(GetRef<TupleGetItem>(tuple_get_item_node), std::move(tuple));
    if (props.body.defined() && props.is_normal()) {
      VLOG(2) << "wrapping tuple get item:" << std::endl
              << PrettyPrint(GetRef<TupleGetItem>(tuple_get_item_node)) << std::endl
              << "with \"on_device\" for VirtualDevice " << props.virtual_device;
      return OnDeviceWithProps(tuple_get_item, props);
    } else {
      return tuple_get_item;
    }
  }

  Expr VisitExpr_(const LetNode* let_node) final {
    auto expr = GetRef<Expr>(let_node);
    std::vector<std::tuple<Let, Expr>> bindings;
    while (const auto* inner_let_node = expr.as<LetNode>()) {
      Let inner_let = GetRef<Let>(inner_let_node);
      Expr value = VisitExpr(inner_let_node->value);
      OnDeviceProps props = GetOnDeviceProps(value);
      if (props.body.defined() && props.is_normal()) {
        VLOG(2) << "revising let-bound expression of let:" << std::endl
                << PrettyPrint(expr) << std::endl
                << "to be fixed to VirtualDevice " << props.virtual_device;
        value = MaybeOnDeviceFixed(props.body, props.virtual_device);
      }
      bindings.emplace_back(inner_let, value);
      expr = inner_let_node->body;
    }
    expr = VisitExpr(expr);
    for (auto itr = bindings.rbegin(); itr != bindings.rend(); ++itr) {
      expr = WithFields(/*let=*/std::move(std::get<0>(*itr)), /*opt_var=*/{},
                        /*opt_value=*/std::move(std::get<1>(*itr)), /*opt_body=*/std::move(expr));
    }
    return expr;
  }

  Expr VisitExpr_(const FunctionNode* function_node) final {
    Expr body = VisitExpr(function_node->body);
    OnDeviceProps props = GetOnDeviceProps(body);
    if (props.body.defined() && props.is_normal()) {
      VLOG(2) << "revising body of function:" << std::endl
              << PrettyPrint(GetRef<Function>(function_node)) << std::endl
              << "to be fixed to VirtualDevice " << props.virtual_device;
      body = MaybeOnDeviceFixed(props.body, props.virtual_device);
    }
    return WithFields(GetRef<Function>(function_node), function_node->params, std::move(body));
  }

  Expr VisitExpr_(const CallNode* call_node) final {
    CallLoweredProps props = GetCallLoweredProps(call_node);
    if (props.lowered_func.defined()) {
      BaseFunc base_func = mod_->Lookup(props.lowered_func);
      if (base_func.as<tir::PrimFuncNode>()) {
        VLOG(2) << "allowing device_copy on PrimFunc arguments and result";
        Array<Expr> new_args;
        new_args.reserve(props.arguments.size());
        for (const auto& arg : props.arguments) {
          Expr new_arg = VisitExpr(arg);
          new_args.push_back(OnDeviceCopyOk(std::move(new_arg)));
        }
        Call new_call = CallLowered(std::move(props.lowered_func), std::move(new_args), props.attrs,
                                    call_node->span);
        return OnDeviceCopyOk(std::move(new_call));
      }
    }
    return ExprMutator::VisitExpr_(call_node);
  }

  /*! \brief Module we are rewriting, so we can lookup global definitions. */
  IRModule mod_;
};

/* =============== Phase 1 =============== */

/*
 * \brief Collects the system of device constraints for all sub-expressions in a module.
 * It is possible some devices remain free and will need to be defaulted by \p DeviceDefaulter.
 *
 * Eg from \code add(%x, %y) \endcode we know \p %x and \p %y must be on the same device. Later,
 * from \code on_device(%x, virtual_device=d) \endcode we know \p %x must be on device \p d, and
 * thus so must \p %y.
 *
 * Constraints can flow in interesting ways. E.g. in:
 * \code
 *   let %f = fn(%x, %y) { add(%x, on_device(%y, virtual_device=d)) }
 *   let %g = fn(%f, %x, %y) { %f(%x, %y) }
 *   %g(%f, %a, %b)
 * \endcode
 * we discover \p %b must be on device \p d.
 */
class DeviceAnalyzer : public ExprVisitor {
 public:
  DeviceAnalyzer(IRModule mod, CompilationConfig config)
      : mod_(std::move(mod)), domains_(std::make_unique<DeviceDomains>(std::move(config))) {}

  /*!
   * \brief Returns the expression-to-device-domain map for all expressions in all the global
   * function definitions in the module. Expressions may have free domains, these will be resolved
   * by \p DeviceDefaulter below.
   */
  std::unique_ptr<DeviceDomains> Analyze() {
    VLOG_CONTEXT << "DeviceAnalyzer";
    for (const auto& kv : mod_->functions) {
      // The global variable and what it is bound to must obviously agree on domain.
      if (const auto* function_node = AsOptimizableFunctionNode(kv.second)) {
        VLOG(2) << "collecting constraints from Relay Function '" << kv.first->name_hint << "'";
        domains_->UnifyExprExact(kv.first, kv.second);
        VisitExpr(GetRef<Function>(function_node));
      } else if (const auto* prim_func_node = kv.second.as<tir::PrimFuncNode>()) {
        VLOG(2) << "collecting constraints from TIR PrimFunc '" << kv.first->name_hint << "'";
        domains_->UnifyExprExact(
            kv.first, DomainForPrimFunc(kv.first, GetRef<tir::PrimFunc>(prim_func_node)));
      } else {
        VLOG(2) << "skipping '" << kv.first->name_hint << "'";
      }
    }
    return std::move(domains_);
  }

 private:
  /*!
   * \brief Return the domain representing \p prim_func which, before lowering, had
   * the Relay \p type.
   */
  DeviceDomainPtr DomainForPrimFunc(const GlobalVar& global_var, const tir::PrimFunc& prim_func) {
    // CAUTION: The prim_func->checked_type() is currently w.r.t. the flattened and DPS form
    // of the prim func, however here we wish to remain within the Relay view of all functions.
    // Thus we'll use the global var who's checked_type is in Relay form.
    auto func_domain = domains_->DomainFor(global_var);  // higher-order

    // TODO(mbs): We don't visit the body of the function -- there's currently nothing to be done.
    const auto* func_type_node = global_var->checked_type().as<FuncTypeNode>();
    ICHECK(func_type_node);
    ICHECK_EQ(func_domain->function_arity(), func_type_node->arg_types.size());

    Array<VirtualDevice> virtual_devices =
        tir::GetPrimFuncArgAndResultConstraints(prim_func, GetRef<FuncType>(func_type_node));

    // Build the implied domain (in terms of the function's Relay type) implied by any memory scope
    // constrains in the function's buffers, for both arguments and results.
    std::vector<DeviceDomainPtr> args_and_result_domains;
    args_and_result_domains.reserve(virtual_devices.size());
    for (size_t i = 0; i < func_type_node->arg_types.size(); ++i) {
      const VirtualDevice& param_virtual_device = virtual_devices[i];
      VLOG(2) << "param_virtual_device[" << i << "] = " << param_virtual_device;
      args_and_result_domains.push_back(domains_->MakeFirstOrderDomain(param_virtual_device));
    }
    const VirtualDevice& ret_virtual_device = virtual_devices.back();
    VLOG(2) << "ret_virtual_device = " << ret_virtual_device;
    args_and_result_domains.push_back(domains_->MakeFirstOrderDomain(ret_virtual_device));

    return domains_->MakeHigherOrderDomain(std::move(args_and_result_domains));
  }

  void VisitExpr_(const CallNode* call_node) final {
    auto call = GetRef<Call>(call_node);

    // We don't care if the call is in pre- or post-lowered form.
    auto vanilla_call = GetAnyCall(call_node);

    // Find the higher-order domain for the callee. See DomainForCallee for the special rules
    // for primitives.
    VisitExpr(vanilla_call->op);
    auto func_domain = domains_->DomainForCallee(call);  // higher-order

    // Build the domain for the function implied by its arguments and call context.
    ICHECK_EQ(func_domain->function_arity(), vanilla_call->args.size()) << PrettyPrint(call);
    std::vector<DeviceDomainPtr> args_and_result_domains;
    args_and_result_domains.reserve(vanilla_call->args.size() + 1);
    for (const auto& arg : vanilla_call->args) {
      args_and_result_domains.emplace_back(domains_->DomainFor(arg));
      VisitExpr(arg);
    }
    args_and_result_domains.emplace_back(domains_->DomainFor(call));
    auto implied_domain =
        domains_->MakeHigherOrderDomain(std::move(args_and_result_domains));  // higher-order

    VLOG(2) << "initial call function domain:" << std::endl
            << domains_->ToString(func_domain) << std::endl
            << "and implied domain:" << std::endl
            << domains_->ToString(implied_domain) << std::endl
            << "for call:" << std::endl
            << PrettyPrint(call);

    // The above must match.
    if (domains_->UnifyOrNull(func_domain, implied_domain) == nullptr) {  // higher-order
      // TODO(mbs): Proper diagnostics.
      LOG(FATAL)
          << "Function parameters and result VirtualDevices do not match those of call. Call:"
          << std::endl
          << PrettyPrint(call) << std::endl
          << "with function virtual devices:" << std::endl
          << domains_->ToString(func_domain) << std::endl
          << "and implied call virtual devices:" << std::endl
          << domains_->ToString(implied_domain);
    }

    VLOG(2) << "final call function domain:" << std::endl
            << domains_->ToString(func_domain) << std::endl
            << "for call:" << std::endl
            << PrettyPrint(call);
  }

  void VisitExpr_(const LetNode* let_node) final {
    Expr expr = GetRef<Let>(let_node);
    // Iteratively visit let nodes to avoid stack overflow.
    while (expr->IsInstance<LetNode>()) {
      Let let = Downcast<Let>(expr);
      // Let var must be same device as value it is bound to.
      domains_->UnifyExprExact(let->var, let->value);  // may be higher-order
      // Let body must be same device as overall let.
      domains_->UnifyExprExact(let, let->body);  // may be higher-order

      VisitExpr(let->var);
      VisitExpr(let->value);

      expr = let->body;
    }

    // Visit the last body
    VisitExpr(expr);
  }

  void VisitExpr_(const FunctionNode* function_node) final {
    // No need to step into fused primitive functions as they are lowered individually according
    // to the devices of all their call sites.
    if (function_node->HasNonzeroAttr(attr::kPrimitive)) {
      return;
    }

    auto function = GetRef<Function>(function_node);
    auto func_domain = domains_->DomainFor(function);  // higher-order

    // The function body domain must match the function result domain.
    domains_->UnifyExprExact(function_node->body,
                             func_domain->function_result());  // may be higher-order

    VLOG(2) << "initial function domain:" << std::endl
            << domains_->ToString(func_domain) << std::endl
            << "and function body domain:" << std::endl
            << domains_->ToString(domains_->DomainFor(function_node->body)) << std::endl
            << "for function:" << std::endl
            << PrettyPrint(function);

    ICHECK_EQ(func_domain->function_arity(), function_node->params.size());
    for (size_t i = 0; i < function_node->params.size(); ++i) {
      // The parameter domains must match the function argument domains.
      domains_->UnifyExprExact(function_node->params[i],
                               func_domain->function_param(i));  // may be higher-order
      VisitExpr(function_node->params[i]);
    }

    // If the function already has VirtualDevice attributes then we can further constrain the
    // function's domain to match them.
    if (!GetFunctionResultVirtualDevice(function_node)->IsFullyUnconstrained()) {
      std::vector<DeviceDomainPtr> args_and_result;
      for (size_t i = 0; i < function_node->params.size(); ++i) {
        args_and_result.emplace_back(
            domains_->ForVirtualDevice(function_node->params[i]->checked_type(),
                                       GetFunctionParamVirtualDevice(function_node, i)));
      }
      args_and_result.emplace_back(domains_->ForVirtualDevice(
          function_node->body->checked_type(), GetFunctionResultVirtualDevice(function_node)));
      auto annotation_domain = domains_->MakeHigherOrderDomain(std::move(args_and_result));
      if (domains_->UnifyOrNull(func_domain, annotation_domain) == nullptr) {  // higher-order
        // TODO(mbs): Proper diagnostics.
        LOG(FATAL) << "Function VirtualDevices are incompatible with its \"on_device\" annotation. "
                      "Function:"
                   << std::endl
                   << PrettyPrint(function) << std::endl
                   << "with function virtual devices:" << std::endl
                   << domains_->ToString(func_domain) << std::endl
                   << "and annotation virtual devices:" << std::endl
                   << domains_->ToString(annotation_domain);
      }
    }

    VisitExpr(function_node->body);

    VLOG(2) << "final function domain:" << std::endl
            << domains_->ToString(func_domain) << std::endl
            << "and function body domain:" << std::endl
            << domains_->ToString(domains_->DomainFor(function_node->body)) << std::endl
            << "for function:" << std::endl
            << PrettyPrint(function);
  }

  void VisitExpr_(const TupleNode* tuple_node) final {
    Tuple tuple = GetRef<Tuple>(tuple_node);
    for (size_t i = 0; i < tuple->fields.size(); i++) {
      auto domain = domains_->DomainFor(tuple->fields[i]);  // may be higher-order
      domains_->UnifyExprCollapsed(tuple, domain);          // collapse to first-order if needed
      VisitExpr(tuple->fields[i]);
    }
  }

  void VisitExpr_(const TupleGetItemNode* tuple_get_item_node) final {
    TupleGetItem tuple_get_item = GetRef<TupleGetItem>(tuple_get_item_node);
    auto domain = domains_->DomainFor(tuple_get_item);  // may be higher-order
    domains_->UnifyExprCollapsed(tuple_get_item_node->tuple,
                                 domain);  // collapse to first-order if needed
    VisitExpr(tuple_get_item_node->tuple);
  }

  class DevicePatternAnalyzer : public PatternVisitor {
   public:
    DevicePatternAnalyzer(DeviceDomains* domains, const ExprNode* adt_node)
        : domains_(domains), adt_node_(adt_node) {}

   private:
    void VisitPattern_(const PatternVarNode* pattern_var_node) final {
      auto var_domain = domains_->DomainFor(pattern_var_node->var);  // may be higher order
      domains_->UnifyExprCollapsed(GetRef<Expr>(adt_node_),
                                   var_domain);  // collapse to first-order if needed
    }

    /*! \brief (Mutable borrow of) the domains for all expressions processed so far. */
    DeviceDomains* domains_;
    /*! \brief The expression for the ADT we are matching over. */
    const ExprNode* adt_node_;
  };

  void VisitPattern(const Pattern& pattern) final {}

  void VisitExpr_(const MatchNode* match_node) final {
    // For match node, we unify the value and the rhs of each clause
    Match match = GetRef<Match>(match_node);
    auto match_domain = domains_->DomainFor(match);  // may be higher-order
    DevicePatternAnalyzer pattern_analyzer(domains_.get(), match->data.get());
    domains_->UnifyExprCollapsed(match->data, match_domain);  // collapse to first-order if needed
    for (const auto& clause : match->clauses) {
      pattern_analyzer.VisitPattern(clause->lhs);
      domains_->UnifyExprExact(clause->rhs, match_domain);
      VisitExpr(clause->rhs);
    }
    VisitExpr(match_node->data);
  }

  void VisitExpr_(const GlobalVarNode* global_var_node) final {
    domains_->DomainFor(GetRef<GlobalVar>(global_var_node));
  }

  void VisitExpr_(const VarNode* var_node) final { domains_->DomainFor(GetRef<Var>(var_node)); }

  void VisitExpr_(const ConstantNode* constant_node) final {
    domains_->DomainFor(GetRef<Constant>(constant_node));
  }

  void VisitExpr_(const ConstructorNode* constructor_node) final {
    // no-op, constructors are handled at their call-sites.
    // TODO(mbs): Assumes eta-expansion
  }

  void VisitExpr_(const IfNode* if_node) final {
    auto ife = GetRef<If>(if_node);
    auto domain = domains_->DomainFor(ife);               // may be higher-order
    domains_->UnifyExprCollapsed(if_node->cond, domain);  // collapse to first-order if needed
    domains_->UnifyExprExact(if_node->true_branch, domain);
    domains_->UnifyExprExact(if_node->false_branch, domain);
    VisitExpr(if_node->cond);
    VisitExpr(if_node->true_branch);
    VisitExpr(if_node->false_branch);
  }

  void VisitExpr_(const OpNode* op) final {
    // no-op, primitive operators are handled at their call-sites.
  }

  void VisitExpr_(const RefCreateNode* ref_create_node) final {
    auto ref_create = GetRef<RefCreate>(ref_create_node);
    auto domain = domains_->DomainFor(ref_create_node->value);  // may be higher-order
    domains_->UnifyExprCollapsed(ref_create, domain);           // collapse to first-order if needed
    VisitExpr(ref_create_node->value);
  }

  void VisitExpr_(const RefReadNode* ref_read_node) final {
    auto ref_read = GetRef<RefRead>(ref_read_node);
    auto domain = domains_->DomainFor(ref_read);               // may be higher-order
    domains_->UnifyExprCollapsed(ref_read_node->ref, domain);  // collapse to first-order if needed
    VisitExpr(ref_read_node->ref);
  }

  void VisitExpr_(const RefWriteNode* ref_write_node) final {
    auto ref_write = GetRef<RefWrite>(ref_write_node);
    auto domain = domains_->DomainFor(ref_write->value);   // may be higher-order
    domains_->UnifyExprCollapsed(ref_write->ref, domain);  // collapse to first-order if needed
    domains_->UnifyExprCollapsed(ref_write, domain);       // collapse to first-order if needed
    VisitExpr(ref_write_node->ref);
    VisitExpr(ref_write_node->value);
  }

  /*! \brief The module we are analyzing. */
  IRModule mod_;
  /*! \brief The domains for all expressions processed so far. */
  std::unique_ptr<DeviceDomains> domains_;
};

/* =============== Phase 2 =============== */

/*!
 * \brief Calls to 'free' "on_device" annotations (ie where both constrain_body=false and
 * constrain_result=false) indicate a device_copy is allowed if required, but no particular
 * device is imposed on the body or the context. At this stage we can attempt to unify the
 * body and device contexts. In this way we can avoid the defaulting rules in \p DeviceDefaulter
 * from choosing default devices which are only going to induce a device copy.
 *
 * TODO(mbs): The order in which we encounter the "on_device" calls can influence the final global
 * device assignment. However we visit global functions in hash map order.
 */
class FreeOnDeviceDefaulter : public ExprVisitor {
 public:
  FreeOnDeviceDefaulter(IRModule mod, std::unique_ptr<DeviceDomains> domains)
      : mod_(std::move(mod)), domains_(std::move(domains)) {}

  std::unique_ptr<DeviceDomains> Default() {
    VLOG_CONTEXT << "FreeOnDeviceDefaulter";
    VLOG(0) << "unifying free on_device annotations";
    for (const auto& kv : mod_->functions) {
      if (const auto* function_node = AsOptimizableFunctionNode(kv.second)) {
        VLOG(2) << "unifying for '" << kv.first->name_hint << "'";
        VisitExpr(GetRef<Function>(function_node));
      } else {
        VLOG(2) << "skipping '" << kv.first->name_hint << "'";
      }
    }
    return std::move(domains_);
  }

 private:
  void VisitExpr_(const CallNode* call_node) final {
    auto call = GetRef<Call>(call_node);
    OnDeviceProps props = GetOnDeviceProps(call_node);
    ExprVisitor::VisitExpr_(call_node);
    if (props.body.defined() && !props.constrain_body && !props.constrain_result) {
      domains_->OptionalUnifyExprExact(call, props.body);
    }
  }

  /*! \brief The module we are processing. */
  IRModule mod_;
  /*! \brief The domains for all expressions.  */
  std::unique_ptr<DeviceDomains> domains_;
};

/*!
 * \brief Ensures every sub-expression in a module has a device type, using both the global
 * default and some local heuristics to avoid unnecessary additional "device_copy" CallNodes.
 *
 * E.g. in:
 * \code
 *   def @main(%x, %y, %z) {
 *     let %a = add(%x, %y);
 *     multiply(%a, on_device(%z, virtual_device=d))
 *   }
 * \endcode
 * we know the parameter \p %z must be on device \p d, but the devices for \p %x and \p %y,
 * and the device for the function result, are still 'free'. The global 'default' device type
 * is first used to 'fix' \p main's result type, which in turn 'fixes' \p %x and \p %y, which
 * in turn 'fixes' the device on which the \p add and \p multiply are executed.
 *
 * TODO(mbs): I think this is deterministic? We do however visit the top-level defs in hashmap
 * order.
 */
class DeviceDefaulter : public ExprVisitor {
 public:
  DeviceDefaulter(IRModule mod, std::unique_ptr<DeviceDomains> domains)
      : mod_(std::move(mod)), domains_(std::move(domains)) {}

  std::unique_ptr<DeviceDomains> Default() {
    VLOG_CONTEXT << "DeviceDefaulter";
    VLOG(0) << "defaulting to VirtualDevice "
            << domains_->config()->default_primitive_virtual_device;
    for (const auto& kv : mod_->functions) {
      if (const auto* function_node = AsOptimizableFunctionNode(kv.second)) {
        VLOG(2) << "defaulting devices for '" << kv.first->name_hint << "'";
        VisitExpr(GetRef<Function>(function_node));
      } else {
        VLOG(2) << "skipping '" << kv.first->name_hint << "'";
      }
    }
    return std::move(domains_);
  }

 private:
  void VisitExpr_(const FunctionNode* function_node) final {
    if (function_node->HasNonzeroAttr(attr::kPrimitive)) {
      return;
    }

    auto function = GetRef<Function>(function_node);
    auto func_domain = domains_->DomainFor(function);  // higher-order
    ICHECK_EQ(func_domain->function_arity(), function_node->params.size());
    if (!domains_->IsFullyConstrained(func_domain)) {
      VLOG(2) << "before defaulting function:" << std::endl << domains_->ToString(func_domain);
      domains_->SetResultDefaultThenParams(func_domain,
                                           domains_->config()->default_primitive_virtual_device);
      VLOG(2) << "after defaulting function:" << std::endl << domains_->ToString(func_domain);
    }
    VisitExpr(function_node->body);
  }

  void VisitExpr_(const CallNode* call_node) final {
    auto call = GetRef<Call>(call_node);

    // We don't care if the call is pre- or post-lowered.
    auto vanilla_call = GetAnyCall(call_node);

    auto func_domain = domains_->DomainForCallee(call);  // higher-order
    ICHECK_EQ(func_domain->function_arity(), vanilla_call->args.size());
    if (!domains_->IsFullyConstrained(func_domain)) {
      // For calls to Relay functions this step is identical to that for VisitExpr_(FunctionNode*)
      // above. But for calls to primitives we may still need to force free domains to be
      // defaulted.
      VLOG(2) << "before defaulting callee:" << std::endl << domains_->ToString(func_domain);
      domains_->SetResultDefaultThenParams(func_domain,
                                           domains_->config()->default_primitive_virtual_device);
      VLOG(2) << "after defaulting callee:" << std::endl << domains_->ToString(func_domain);
    }
    return ExprVisitor::VisitExpr_(call_node);
  }

  void VisitExpr_(const LetNode* let_node) final {
    Expr expr = GetRef<Let>(let_node);
    // Iteratively visit let nodes to avoid stack overflow.
    while (expr->IsInstance<LetNode>()) {
      Let let = Downcast<Let>(expr);
      // If the let-var device is still free force it to match the overall let.
      auto let_domain = domains_->DomainFor(let);  // may be higher-order
      VirtualDevice let_virtual_device = domains_->ResultVirtualDevice(let_domain);
      ICHECK(!let_virtual_device->IsFullyUnconstrained());
      auto let_var_domain = domains_->DomainFor(let->var);  // may be higher-order
      if (!domains_->IsFullyConstrained(let_var_domain)) {
        VLOG(2) << "before defaulting let-var:" << std::endl << domains_->ToString(let_var_domain);
        domains_->SetDefault(let_var_domain, let_virtual_device);
        VLOG(2) << "after defaulting let-var:" << std::endl << domains_->ToString(let_var_domain);
      }
      VisitExpr(let->var);
      VisitExpr(let->value);
      expr = let->body;
    }
    VisitExpr(expr);
  }

  /*! \brief The module we are processing. */
  IRModule mod_;
  /*! \brief The domains for all expressions.  */
  std::unique_ptr<DeviceDomains> domains_;
};

/* =============== Phase 3 =============== */

/*!
 * \brief Inserts missing "device_copy" CallNodes, and ensures the device type of every
 * sub-expression in a module can be easily recovered by a later transformation using simple
 * lexical scoping rules (e.g. for memory planning).
 *
 * - Discard any existing "on_device" CallNodes since their job is done. Similarly, discard
 *   any existing "device_copy" CallNodes which are no-ops.
 *
 * - Functions are given "param_virtual_devices" and "result_virtual_device" attributes to capture
 *   the device type for its parameters and result.
 *
 * - Additional "device_copy" CallNodes are inserted wherever there's a transition between
 *   storage device types. Since the DeviceAnalyzer phase succeeded this can only happen
 *   where the original program explicitly allowed a transition using an "on_device" CallNode.
 *   That is, we do not not try to 'fix' a program with inconsistent devices.
 *
 * - Additional "on_device" CallNodes are inserted so that a later transform can discover
 *   the device for an arbitrary sub-expression by looking only for the lexically enclosing
 *   "on_device" CallNode or "on_device" function attribute. In particular, since function
 *   arguments and let-bound expressions can be on a device different from the function
 *   or let body itself we will insert "on_device" CallNodes to spell out any differences. This
 *   applies even to the argument to a "device_copy" CallNode, which may look pedantic but
 *   keeps downstream processing simple. The "on_device" calls should be removed before code gen,
 *   which is easily done on-the-fly.
 *
 * - Update memory scopes in PrimFunc buffer maps.
 *
 * For example, we'll end up with programs that look like:
 * \code
 *   def @main(%x, %y, param_virtual_devices=[...], result_virtual_device=...) {
 *     let %a = on_device(..., virtual_device=..., is_fixed=True)
 *     @f(%a, device_copy(on_device(..., virtual_device=..., is_fixed=True),
 *                        src_virtual_device=..., dst_virtual_device=...))
 *   }
 * \endcode
 */
class DeviceCapturer : public ExprMutator {
 public:
  DeviceCapturer(IRModule mod, std::unique_ptr<DeviceDomains> domains)
      : mod_(std::move(mod)), domains_(std::move(domains)) {}

  IRModule Capture() {
    VLOG_CONTEXT << "CaptureDevices";
    IRModule result(/*functions=*/{}, mod_->type_definitions, mod_->Imports(), mod_->source_map,
                    mod_->attrs);
    for (const auto& kv : mod_->functions) {
      if (const auto* function_node = AsOptimizableFunctionNode(kv.second)) {
        VLOG(2) << "capturing devices for Relay Function '" << kv.first->name_hint << "'";
        result->Add(kv.first, Downcast<Function>(Mutate(GetRef<Function>(function_node))));
      } else if (const auto* prim_func_node = kv.second.as<tir::PrimFuncNode>()) {
        VLOG(2) << "capturing devices for TIR PrimFunc '" << kv.first->name_hint << "'";
        auto prim_func = GetRef<tir::PrimFunc>(prim_func_node);
        tir::PrimFunc new_prim_func = UpdatePrimFunc(kv.first, prim_func);
        VLOG(2) << "Rewritten prim func:" << std::endl
                << PrettyPrint(prim_func) << std::endl
                << "to:" << std::endl
                << PrettyPrint(new_prim_func);
        result->Add(kv.first, std::move(new_prim_func));
      } else {
        VLOG(2) << "skipping '" << kv.first->name_hint << "'";
        result->Add(kv.first, kv.second);
      }
    }
    return result;
  }

 private:
  /*!
   * \brief Returns \p prim_func updated to capture any memory scope's implied by its device
   * domain.
   */
  tir::PrimFunc UpdatePrimFunc(const GlobalVar& global_var, const tir::PrimFunc& prim_func) {
    // CAUTION: Same caution as for DeviceAnalyzer::DomainForPrimFunc.
    auto func_domain = domains_->DomainFor(global_var);
    ICHECK(func_domain->is_higher_order());

    const auto* func_type_node = global_var->checked_type().as<FuncTypeNode>();
    ICHECK(func_type_node);
    ICHECK_EQ(func_domain->function_arity(), func_type_node->arg_types.size());

    std::vector<VirtualDevice> arg_and_result_virtual_devices;
    arg_and_result_virtual_devices.reserve(func_type_node->arg_types.size() + 1);
    for (size_t i = 0; i < func_type_node->arg_types.size(); ++i) {
      VirtualDevice param_virtual_device =
          domains_->ResultVirtualDevice(func_domain->function_param(i));
      VLOG(2) << "param_virtual_device[" << i << "] = " << param_virtual_device;
      arg_and_result_virtual_devices.push_back(param_virtual_device);
    }
    VirtualDevice ret_virtual_device =
        domains_->ResultVirtualDevice(func_domain->function_result());
    VLOG(2) << "ret_virtual_device = " << ret_virtual_device;
    arg_and_result_virtual_devices.push_back(ret_virtual_device);

    return tir::ApplyPrimFuncArgAndResultConstraints(prim_func, GetRef<FuncType>(func_type_node),
                                                     arg_and_result_virtual_devices);
  }

  // Nothing interesting for VarNode, ConstantNode, GlobalVarNode, OpNode and ConstructorNode

  Expr VisitExpr_(const TupleNode* tuple_node) final {
    auto tuple = GetRef<Tuple>(tuple_node);
    Array<Expr> fields;
    fields.reserve(tuple_node->fields.size());
    for (const auto& field : tuple_node->fields) {
      fields.push_back(VisitChild(tuple, field));
    }
    return WithFields(std::move(tuple), std::move(fields));
  }

  Expr VisitExpr_(const FunctionNode* function_node) final {
    if (function_node->HasNonzeroAttr(attr::kPrimitive)) {
      return GetRef<Function>(function_node);
    }

    auto function = GetRef<Function>(function_node);
    auto func_domain = domains_->DomainFor(function);  // higher-order
    VLOG(2) << "capturing function:" << std::endl
            << PrettyPrint(function) << std::endl
            << "with domain:" << std::endl
            << domains_->ToString(func_domain);

    // Gather the parameter and result device types for the function attributes.
    ICHECK_EQ(func_domain->function_arity(), function_node->params.size());
    VirtualDevice result_virtual_device = domains_->ResultVirtualDevice(func_domain);
    ICHECK(!result_virtual_device->IsFullyUnconstrained());
    Array<VirtualDevice> param_virtual_devices;
    param_virtual_devices.reserve(function_node->params.size());
    for (size_t i = 0; i < function_node->params.size(); ++i) {
      VirtualDevice param_virtual_device =
          domains_->ResultVirtualDevice(func_domain->function_param(i));
      ICHECK(!param_virtual_device->IsFullyUnconstrained());
      param_virtual_devices.push_back(param_virtual_device);
    }

    // Rewrite the body. Note that the body may have begun with an "on_device" so
    // be prepared to insert a "device_copy".
    Expr body = VisitChild(
        /*lexical_virtual_device=*/result_virtual_device,
        /*expected_virtual_device=*/result_virtual_device,
        /*child_virtual_device=*/GetVirtualDevice(function_node->body), function_node->body);

    Function func = WithFields(GetRef<Function>(function_node), std::move(function_node->params),
                               std::move(body));
    return FunctionOnDevice(func, std::move(param_virtual_devices),
                            std::move(result_virtual_device));
  }

  Expr VisitExpr_(const CallNode* call_node) final {
    auto call = GetRef<Call>(call_node);

    // We don't care if the call is pre- or post-lowered
    // (However we'll preserve the form in the result below.)
    auto vanilla_call = GetAnyCall(call_node);

    VirtualDevice call_virtual_device = GetVirtualDevice(call);

    auto on_device_props = GetOnDeviceProps(call_node);
    if (on_device_props.body.defined()) {
      // We're done with the original "on_device" calls and can pinch them out.
      // Note that this step has already been simulated by GetDeviceType.
      return VisitExpr(on_device_props.body);
    }

    DeviceCopyProps device_copy_props = GetDeviceCopyProps(call_node);
    if (device_copy_props.body.defined()) {
      VirtualDevice src_virtual_device =
          domains_->config()->CanonicalVirtualDevice(device_copy_props.src_virtual_device);
      VirtualDevice dst_virtual_device =
          domains_->config()->CanonicalVirtualDevice(device_copy_props.dst_virtual_device);
      ICHECK_EQ(call_virtual_device, dst_virtual_device);
      if (src_virtual_device == dst_virtual_device) {
        // We can pinch out existing "device_copy" CallNodes if their source and destinations
        // match.
        return VisitExpr(device_copy_props.body);
      } else {
        return VisitChild(/*lexical_virtual_device=*/dst_virtual_device,
                          /*expected_virtual_device=*/dst_virtual_device,
                          /*child_virtual_device=*/src_virtual_device, device_copy_props.body);
      }
    }

    // Generic call.
    auto func_domain = domains_->DomainForCallee(call);  // higher-order
    VLOG(2) << "considering call:" << std::endl
            << PrettyPrint(call) << std::endl
            << "in virtual device " << call_virtual_device
            << " with function virtual devices:" << std::endl
            << domains_->ToString(func_domain);
    VirtualDevice result_virtual_device = domains_->ResultVirtualDevice(func_domain);
    ICHECK(!result_virtual_device->IsFullyUnconstrained());

    // The callee is on the current device.
    Expr op = VisitChild(
        /*lexical_virtual_device=*/call_virtual_device,
        /*expected_virtual_device=*/call_virtual_device,
        /*child_virtual_device=*/result_virtual_device, vanilla_call->op);

    // Each argument can be on the device for the corresponding function parameter. However if
    // any of those differ from the overall call device then wrap them in an "on_device" to
    // help downstream transforms track devices lexically.
    Array<Expr> args;
    args.reserve(vanilla_call->args.size());
    ICHECK_EQ(func_domain->function_arity(), vanilla_call->args.size());
    for (size_t i = 0; i < vanilla_call->args.size(); ++i) {
      VirtualDevice param_virtual_device =
          domains_->ResultVirtualDevice(func_domain->function_param(i));
      ICHECK(!param_virtual_device->IsFullyUnconstrained())
          << "for parameter " << i << " for call:" << std::endl
          << PrettyPrint(call);
      args.push_back(VisitChild(/*lexical_virtual_device=*/call_virtual_device,
                                /*expected_virtual_device=*/param_virtual_device,
                                /*child_virtual_device=*/GetVirtualDevice(vanilla_call->args[i]),
                                vanilla_call->args[i]));
    }

    if (call_node->op == CallLoweredOp()) {
      Call new_call =
          CallLowered(Downcast<GlobalVar>(op), args, /*call_lowered_attrs=*/{}, /*span=*/{});
      return WithFields(call, std::move(new_call->op), std::move(new_call->args));
    } else {
      return WithFields(call, std::move(op), std::move(args));
    }
  }

  Expr VisitExpr_(const LetNode* let_node) final {
    Expr expr = GetRef<Expr>(let_node);
    // Iterate through chained lets, provided they all agree on their device type.
    VirtualDevice let_virtual_device = GetVirtualDevice(expr);
    std::vector<std::tuple<Var, Expr, Span>> bindings;
    while (const auto* inner_let_node = expr.as<LetNode>()) {
      Expr inner_let = GetRef<Let>(inner_let_node);
      if (GetVirtualDevice(inner_let) != let_virtual_device) {
        // We have a device transition which needs to be handled.
        break;
      }
      // The let-bound value can be on a different device than the overall let.
      // By using the fully-unconstrained virtual device for the 'lexical' scope we'll force the
      // let-bound value to *always* be wrapped by an "on_device" (see introductory comment for
      // motivation.)
      Expr value = VisitChild(/*lexical_virtual_device=*/VirtualDevice::FullyUnconstrained(),
                              /*expected_virtual_device=*/GetVirtualDevice(inner_let_node->var),
                              /*child_virtual_device=*/GetVirtualDevice(inner_let_node->value),
                              inner_let_node->value);
      bindings.emplace_back(inner_let_node->var, value, inner_let_node->span);
      expr = inner_let_node->body;
    }
    Expr body = VisitChild(/*lexical_virtual_device=*/let_virtual_device,
                           /*expected_virtual_device=*/let_virtual_device,
                           /*child_virtual_device=*/GetVirtualDevice(expr), expr);
    for (auto itr = bindings.rbegin(); itr != bindings.rend(); ++itr) {
      body = Let(/*var=*/std::get<0>(*itr), /*value=*/std::get<1>(*itr), body,
                 /*span=*/std::get<2>(*itr));
    }
    return body;
  }

  Expr VisitExpr_(const IfNode* if_node) final {
    auto ife = GetRef<If>(if_node);
    Expr cond = VisitChild(ife, if_node->cond);
    Expr true_branch = VisitChild(ife, if_node->true_branch);
    Expr false_branch = VisitChild(ife, if_node->false_branch);
    return WithFields(std::move(ife), std::move(cond), std::move(true_branch),
                      std::move(false_branch));
  }

  Expr VisitExpr_(const TupleGetItemNode* tuple_get_item_node) final {
    auto tuple_get_item = GetRef<TupleGetItem>(tuple_get_item_node);
    Expr tuple = VisitChild(tuple_get_item, tuple_get_item_node->tuple);
    return WithFields(std::move(tuple_get_item), std::move(tuple));
  }

  Expr VisitExpr_(const RefCreateNode* ref_create_node) final {
    auto ref_create = GetRef<RefCreate>(ref_create_node);
    Expr value = VisitChild(ref_create, ref_create_node->value);
    return WithFields(std::move(ref_create), std::move(value));
  }

  Expr VisitExpr_(const RefReadNode* ref_read_node) final {
    auto ref_read = GetRef<RefRead>(ref_read_node);
    Expr ref = VisitChild(ref_read, ref_read_node->ref);
    return WithFields(std::move(ref_read), std::move(ref));
  }

  Expr VisitExpr_(const RefWriteNode* ref_write_node) final {
    auto ref_write = GetRef<RefWrite>(ref_write_node);
    Expr ref = VisitChild(ref_write, ref_write_node->ref);
    Expr value = VisitChild(ref_write, ref_write_node->value);
    return WithFields(std::move(ref_write), std::move(ref), std::move(value));
  }

  Expr VisitExpr_(const MatchNode* match_node) final {
    auto match = GetRef<Match>(match_node);
    Expr data = VisitChild(match, match_node->data);
    Array<Clause> clauses;
    clauses.reserve(match_node->clauses.size());
    for (const auto& clause : match_node->clauses) {
      Pattern lhs = VisitPattern(clause->lhs);  // actually a no-op, so we're not checking vars
      Expr rhs = VisitChild(match, clause->rhs);
      clauses.push_back(Clause(lhs, rhs));
    }
    return WithFields(std::move(match), std::move(data), std::move(clauses));
  }

  VirtualDevice GetVirtualDevice(const Expr& expr) {
    // Look through any "on_device" CallNodes, to mimic how we will be pinching them out.
    OnDeviceProps props = GetOnDeviceProps(expr);
    Expr true_expr = props.body.defined() ? props.body : expr;
    ICHECK(domains_->contains(true_expr));
    // If expr is higher order we'll return only the result domain's device.
    VirtualDevice virtual_device = domains_->ResultVirtualDevice(domains_->DomainFor(true_expr));
    ICHECK(!virtual_device->IsFullyUnconstrained())
        << "no VirtualDevice was determined for expression:" << std::endl
        << PrettyPrint(true_expr);
    return std::move(virtual_device);
  }

  /*!
   * \brief Reconcile the \p child_virtual_device for \p child with both the \p
   * expected_virtual_device (as required by the expression context the \p child is in) and the \p
   * lexical_virtual_device (as a downstream transform would infer based only on lexically enclosing
   * "on_device" CallNodes and function attributes.) Generally \p lexical_virtual_device and \p
   * expected_virtual_device are the same by definition, but may differ in arguments to  functions
   * and let-bound expressions.
   *
   * If \p child_virtual_device differs from \p expected_virtual_device, wrap it as:
   * \code
   *   device_copy(on_device(child', virtual_device=child_virtual_device),
   *               src_dev_type=child_virtual_device, dst_dev_type=expected_virtual_device)
   * \endcode
   * (where child is rewritten to child'). Note the pedantic spelling out of "on_device" on the
   * child.
   *
   * If \p expected_virtual_device differs from \p lexical_virtual_device, then (also) wrap
   * the expression as:
   * \code
   *   on_device(..., virtual_device=expected_virtual_device)
   * \endcode
   *
   * TODO(mbs): There's no attempt at sharing here. If usage of child's node could be wrapped
   * by a "device_copy", even though those copies will generally all be to the same destination
   * device.
   */
  Expr VisitChild(const VirtualDevice& lexical_virtual_device,
                  const VirtualDevice& expected_virtual_device,
                  const VirtualDevice& child_virtual_device, const Expr& child) {
    ICHECK(!expected_virtual_device->IsFullyUnconstrained());
    if (child->IsInstance<OpNode>() || child->IsInstance<ConstructorNode>()) {
      // Primitive operators and contructors don't need to be rewritten and can have a
      // different domain at each call site.
      return child;
    }
    Expr result = VisitExpr(child);
    if (child_virtual_device != expected_virtual_device) {
      VLOG(2) << "creating " << DeviceCopyOp()->name << " from virtual device "
              << child_virtual_device << " to virtual device " << expected_virtual_device
              << " for:" << std::endl
              << PrettyPrint(result);
      // Also wrap the child in an "on_device" so downstream transforms can track devices
      // lexically.
      result = MaybeOnDeviceFixed(result, child_virtual_device);
      result = DeviceCopy(result, child_virtual_device, expected_virtual_device);
    }
    if (expected_virtual_device != lexical_virtual_device) {
      VLOG(2) << "creating " << OnDeviceOp()->name << " for virtual device "
              << expected_virtual_device << " for:" << std::endl
              << PrettyPrint(result);
      result = MaybeOnDeviceFixed(result, expected_virtual_device);
    }
    return result;
  }

  /*!
   * Common case of visiting a direct \p child of \p parent where by default the \p child
   * is expected to be on the same device as the \p parent.
   */
  Expr VisitChild(const Expr& parent, const Expr& child) {
    VirtualDevice expected_virtual_device = GetVirtualDevice(parent);
    VirtualDevice child_virtual_device = GetVirtualDevice(child);
    return VisitChild(expected_virtual_device, expected_virtual_device, child_virtual_device,
                      child);
  }

  /*! \brief Module we are rewriting, so we can lookup global variables. */
  IRModule mod_;
  /*! \brief Device domain for every expression from DeviceAnalyzer. */
  std::unique_ptr<DeviceDomains> domains_;
};

/*! \brief Rewrite the "on_device" calls (and implicitly re-type-check). */
tvm::transform::Pass Rewrite() {
  auto pass_func = [](Function f, IRModule m, transform::PassContext ctxt) {
    return Downcast<Function>(RewriteOnDevices(std::move(m)).Mutate(f));
  };
  return tvm::relay::transform::CreateFunctionPass(pass_func, 0, "PlanDevicesRewrite", {});
}

/*! \brief Run the remaining phases. */
tvm::transform::Pass PlanDevicesCore(CompilationConfig config) {
  return tvm::transform::CreateModulePass(
      [config = std::move(config)](IRModule mod,
                                   tvm::transform::PassContext pass_cnxt) -> IRModule {
        // Collect the system of constraints for every sub-expression using existing "on_device"
        // and "device_copy" calls.
        std::unique_ptr<DeviceDomains> domains = DeviceAnalyzer(mod, config).Analyze();
        VLOG(3) << "Domains after analysis:" << std::endl << domains->ToString();

        // Choose sensible default devices for every sub-expression if otherwise unconstrained
        // by existing "on_device" or "device_copy" calls.
        domains = FreeOnDeviceDefaulter(mod, std::move(domains)).Default();
        domains = DeviceDefaulter(mod, std::move(domains)).Default();
        VLOG(3) << "Domains after defaulting: " << std::endl << domains->ToString();

        // Insert "device_copy" and "on_device" CallNodes where needed to unambiguously capture
        // the above map, and attach additional "param_virtual_devices" and "result_virtual_device"
        // attributes to all function definitions.
        return DeviceCapturer(mod, std::move(domains)).Capture();
      },
      /*opt_level=*/0, "PlanDevicesCore", {});
}

}  // namespace

/* =============== Driver =============== */

// This function is declared in the public <tvm/relay/transform.h>.
tvm::transform::Pass PlanDevices(CompilationConfig config) {
  std::vector<Pass> passes;
  passes.emplace_back(Rewrite());
  passes.emplace_back(PlanDevicesCore(std::move(config)));
  return tvm::transform::Sequential(passes, "PlanDevices");
}

TVM_REGISTER_GLOBAL("relay._transform.PlanDevices").set_body_typed(PlanDevices);

}  // namespace transform
}  // namespace relay
}  // namespace tvm

redpajama_set_name: RedPajamaGithub


text:
Ulla Ekblad-Forsgren, född 17 april 1937, är en svensk översättare, bosatt i Lund. Hon översätter från tyska och har framför allt gjort sig känd som en kongenial översättare av Friederike Mayröcker. År 2011 var således hennes översättning av Mayröckers diktbok Scardanelli en av finalisterna till Årets översättning.

Översättningar (urval)
 Marcel Beyer: Flyghundar (Flughunde) (Bonnier, 2000)
 Judith Hermann: Sommarhus, senare (Sommerhaus, später) (Ordfront, 2000)
 Marcel Beyer: Spioner (Spione) (Bonnier, 2002)
 Judith Hermann: Bara spöken (Nichts als Gespenster) (Ordfront, 2004)
 Michael Wallner: April i Paris (April in Paris) (Wahlström & Widstrand, 2006)
 Marcel Beyer: Kaltenburg (Kaltenburg) (Bonnier, 2008)
 Natten då muren föll: 25 tyska författare berättar om 9 november 1989 (Die Nacht, in der die Mauer fiel) (Ersatz, 2009)

Priser
 Albert Bonniers 100-årsminne 2008
 Stiftelsen Natur & Kulturs översättarpris 2013
 Österrikiska statens översättarpris 2016
Svenska Akademiens översättarpris 2020

Litteratur
 Cecilia Hansson: "Sammanhanget är det viktigaste : poeten Cecilia Hansson i ett samtal med översättaren Ulla Ekblad-Forsgren", i tidskriften Lyrikvännen, årg. 51 (2004): nr 2, s. 39-45

Referenser

Noter

Övriga källor
 Översättarens presentation på Översättarcentrums hemsida. Läst 16 april 2014.
 "Månadens översättare: Ulla Ekblad-Forsgren" på Översättarsektionen i Sveriges författarförbunds hemsida. 1 december 2013. Läst 29 januari 2019.

Svenska översättare
Översättare från tyska
 Svenskspråkiga översättare
Mottagare av Stiftelsen Natur & Kulturs översättarpris
Födda 1937
Levande personer
Kvinnor

redpajama_set_name: RedPajamaWikipedia


text:
Since Tunisia's first international association football match on 2 June 1957 against Libya, there have been 12 occasions when a Tunisian player has scored three or more goals (a hat-trick) in a game. The first hat-trick was scored by Moncef Chérif against Chinese Taipei on 18 August 1960.

List

References

External links

 Tunisia national football team statistics and records: hat tricks

Hat-tricks
Tunisia
Tunisia


- Adds processing of VIIRS J1.
- Updates to the OCSSW GUI help pages.
Now supports Viirs J1 in both GUI and command line.
or from command line do "update_luts.py aqua" or "update_luts.py terra".
directory, it will automically create this directory and initialize it with the package settings and defaults.
of your previous version's '.seadas' directory over into the '.seadas' directory.


Our bus picked us up at about 8 AM, and we rode east — a lot farther than we thought we were going. The long ride over gravel roads gave Mrs. Moses some car-sickness, which she handled like a champ. We were heading to the Beagle Channel en route to Gable Island.
On the way to Gable Island. In the distance, Chile's Andes mountains.
Our guide told us that this is called "flat tree," a very famous tree in these parts. The winds get so strong that part of the trunk rests on the ground.
We started the trip with rafting, except this time we had a very different group. Our raft consisted of a German couple, two French women we met on yesterday's hike, an older couple from Washington D.C, and Marcy and me. I was appointed the captain, which meant that I was supposed to sit in the back of the boat and steer.
I will admit at the outset that I didn't really know how to steer. I was given some rudimentary instructions, which didn't seem that complicated. Everyone else was supposed to row in sync. My lack of experience did not help to compensate for what transpired. But I maintain that I share only part of the blame.
About a minute after we launched, the Germans decided not to row, because "the current was moving us." The elderly D.C. couple tired out immediately and alternated between not rowing and casually dipping their oars in the water. The French were not rowing very hard but put a great deal of effort into yelling at the Germans to paddle.
I will describe the raft's path through the water as similar to a dud firework in slow motion; spinning in a seemingly random and dangerous way. There were sharp branches overhanging the shore which hurtled toward us. The other raft, which mostly consisted of a family of 6 from Argentina and a true river guide, stopped paddling to watch us. And even though they stopped paddling, they were still moving downriver at a faster pace than we were.
While the actual rafting didn't go smoothly, it was hard not to appreciate the view.
Our rafting guide wisely decided that he needed to transfer to our raft and the two French women needed to move over to the other raft. Or maybe it was unwise because he quickly saw that we had a huge labor shortage and he had to do a lot of "compensation" paddling. I don't think I'm exaggerating when I say that on the rest of the raft trip, the guide and Marcy and I were the only ones who were actually rowing.
We finally got to our destination, which was the Beagle channel. The sun was out, we had blue skies, and the temperature was pretty mild. We lucked out with the weather. A motorboat was waiting to take us across the channel to Gable Island, where lunch was waiting. Expertly grilled fish, beef, vegetables, and well-earned red wine.
After lunch, the man from D.C. said he'd heard that we did a similar trip yesterday. I said that we had. He then asked, "Why would you choose to do it again?" It was not what he thought he'd signed up for.
We then started the second part of the day, our hike. I really liked this hike, because the trail took us across the island, from the south end to the north end. Unlike yesterday's hike in a national park, this one had no markers or signs. On the entire hike, we never saw another person; we had the whole island to ourselves. Like yesterday, no man-made noise. Pure nature. Marcy's comment was that this is what the earth is like when people don't spoil it.
A beaver dam. The government foolishly released beavers into the wild, and because there are no natural predators in this part of the world, they are causing problems to the ecosystem.
After a steep climb, I turned around to take this picture. You can make out the trail in the distance.
At the summit, there was a steep cliff with an unusual pattern. One side of the island is covered with these cliffs, which reminded early explorers of gables. That's how the island got its name.
When we finished the hike, the boat was waiting to take us to Martillo island. As we pulled up to the shore, we saw several Magellanic penguins. They had already hatched their eggs and were protecting them, and we weren't allowed to walk up to them, but we got pretty close. Penguins are one of those animals (like giant pandas) that are so unusual that they don't seem real. I'd seen them in aquariums and zoos, but it was surreal to just see them hanging out in nature and doing their thing.
I haven't really written that much about Ushuaia, other than it reminded us of the 90's TV show Northern Exposure. We've been heading downtown in the evenings to eat dinner. It's touristy, but it's also a pretty cool place. The thing that strikes me about it is you can look in any direction and it looks like a postcard. Water, mountains, and buildings of every hue make for a really beautiful city. Heading home from dinner, we caught an amazing sunset, with a sky filled with purple, pink, orange, and several colors in between.
Marcy took this one outside of our hotel room.


The degree of Doctor Honoris causa is the name of the honorary degree offered by Sokoine University of Agriculture.
An honorary degree is an academic degree for which a University has waived the usual requirements such as study and the passing of examinations. It is usually conferred as a way of honoring a distinguished person's contribution to a specific field or society in general.
The SUA honorary degree is conferred upon recommendation by the Senate and approval by Council of Sokoine University of Agriculture to a person who, in the opinion of Senate and Council, has rendered distinguished service in the advancement of any branch of learning or has otherwise rendered himself/herself worthy of such a degree through other engagements for the improvement of society.
Since the establishment of SUA as a fully-fledged University in 1984, the degree has only been conferred Twice.
The first recipient, in 1997, was Mwalimu Julius Kambarage Nyerere, first President of the United Republic of Tanzania and the first Chancellor of Sokoine University of Agriculture.
The second recipient, in 2013, was Prof. Geoffrey Raphael Vehaeli Mmari, first Vice Chancellor of Sokoine University of Agriculture.
ORATION By Prof. Kallunde Pilly Sibuga, PhD On the Occasion of Conferring an Honorary Degree on PROF. GEOFFREY RAPHAEL VEHAELI MMARI 29th November, 2013 Morogoro, Tanzania.
Acceptance Speech by Prof. Geoffrey Raphael Vehaeli Mmari on the Occasion of His Conferment of Doctor Honoris Causa Degree of the Sokoine University of Agriculture during the 29th Graduation Ceremony, 29th November 2013. Morogoro, Tanzania.


text:
Bohemian Rhapsody for Concert Band
By Glee Cast Featuring Jonathan Groff, Queen, The Braids
Arte Nova Music Lab
(1) 1 Review Write a Review
https://www.sheetmusicplus.com/title/bohemian-rhapsody-for-concert-band-digital-sheet-music/22046076?aff_id=69435
Flute, Oboe, Clarinet, English Horn, Bassoon, Alto Sax, Tenor Sax, Baritone Sax, Soprano Sax, Trumpet, Trombone, Tuba, Horn in F, Euphonium, Timpani, Percussion Ensemble, Concert Band - Advanced Intermediate - Digital Download
By Glee Cast Featuring Jonathan Groff, Queen, The Braids. Arranged by Javier Martínez Maya. Score, Set of Parts. 79 pages. Published by Arte Nova Music Lab (H0.1063661-SC000999591).
Item Number: H0.1063661-SC000999591
"Bohemian Rhapsody" is a song by the British rock band Queen. It was written by Freddie Mercury for the band's 1975 album A Night at the Opera. The song is a six-minute suite, notable for its lack of a refraining chorus and consisting of several sections: an intro, a ballad segment, an operatic passage, a hard rock part and a reflective coda. "Bohemian Rhapsody" is one of the few songs to emerge from the 1970s progressive rock movement to achieve widespread commercial success and appeal to a mainstream audience.
"Bohemian Rhapsody" topped the UK Singles Chart for nine weeks and had sold more than a million copies by the end of January 1976. In 1991, after Mercury's death, it topped the charts for another five weeks, eventually becoming the UK's third best-selling single of all time. It is also the only song to reach the UK Christmas number one twice by the same artist. It also topped the charts in countries including Canada, Australia, New Zealand, Ireland, and the Netherlands, and sold over six million copies sold worldwide. In the United States, the song peaked at number nine in 1976, but reached a new peak of number two on theBillboard Hot 100 after being used in the film Wayne's World (1992). In 2018, the release of Queen biopic Bohemian Rhapsody brought the song renewed popularity and chart success worldwide.
Taken from https://en.wikipedia.org/wiki/Bohemian_Rhapsody
Preview: Bohemian Rhapsody for Concert Band
Bohemian Rhapsody for Concert Band (Sample #1)
Top Selling Band Sheet Music
Profanation
America's Favorite Christmas Carols...
Your Favorite Christmas Hymns and C...
Sleigh Ride (String Quartet Score a...
Merry-Go-Round of Life - Howl's Mov...
West Side Story (Full Score)
The Avengers Theme by Alan Silvestr...
The Hymn of Acxiom
Frosty The Snowman (Orchestra/ Conc...
Star Wars (Suite for Orchestra) - D...
O Holy Night (string quartet)
Harry Potter And The Goblet Of Fire...
Never Gonna Give You Up (Rick Astle...
English Horn Sheet Music
Concert Band Music Percussion Ensemble Music
August 22, 2021 What a great version!!
Nice work for Band!
One person found this review helpful.

redpajama_set_name: RedPajamaCommonCrawl


gold ore cone crusher supplier in angola . mobile limestone cone crusher suppliers in odysseus project. mobile limestone cone crusher supplier malaysia oct. 25th. mobile gold ore cone crusher for hire . sep 1,2017 concrete jaw crusher manufacturer malaysia .
portable gold crushers in malaysia gold ore crusher, roll crusher, impact crusher,, gold ore crusher machine supplier in malaysia, exporter in malaysia .
portable gold ore jaw crusher suppliers malaysia. used mobile crusher suppliers copper and gold ore in malaysia crusher machine malaysia . get price online.
resultsofmobile dolomite impact crusher for hire angola · por le coal coneyour in angola grinding equipmentrussia gold ore crusher machine supplier in malaysia.


Dale Earnhardt Jr autographed the hood of this very sharp Mesma and Color Chrome 1-24th scale Nascar Diecast Collectible.
Manufactured by Motorsports Authentics for Race Fans Collectibles with excellent detail inside and out. When you look at this beauty it is easy to see why it would be such a great addition to any Dale Jr. collection. The value is up with it being autographed by Dale Jr.


text:
Iggy Pop returns with haunting single, "Free"
He's also announced a new album.
Image: Andrea Raffin / Shutterstock.com
Iggy Pop has released "Free," the title track of his forthcoming 18th solo album. It's also his latest song since his 2018 collaboration with Underworld.
The track is an ambient slow-burner, with trumpets and horns billowing over droning synths to create cinematic soundscapes. Punctuated by a spoken-word refrain of Iggy declaring "I wanna be free," the song teases a new sonic direction for the prolific musician.
Free follows 2016's Post Pop Depression, and is described in a press release as a "uniquely somber and contemplative entry in the Iggy Pop canon." It also features contributions from jazz trumpeter Leron Thomas and guitarist Noveller.
"By the end of the tours following Post Pop Depression, I felt sure that I had rid myself of the problem of chronic insecurity that had dogged my life and career for too long. But I also felt drained. […] I wanted to be free," Iggy explained in a statement.
"I know that's an illusion, and that freedom is only something you feel, but I have lived my life thus far in the belief that that feeling is all that is worth pursuing […]. So this album just kind of happened to me, and I let it happen."
The new record arrives September 6 via Loma Vista/Caroline International. In the meantime, listen to "Free" below:


Visa Compliance. Due to the urgency of this vacancy, our client is unable to go down the route of work permits, visas or sponsorship. It is therefore ESSENTIAL that all candidates have FULL AUTHORITY to work in Ireland. The Role. Our clients, a world renowned organisation in their field, are seeking to recruit an enthusiastic, dynamic Purchasing Manager to join their busy team. The Purchasing Manager will assume overall responsibility for achieving Company goals based on corporate inventory requirements and sales forecasts for the year ahead. The candidate will work to ensure materials, work in progress and finished goods are purchased and maintained under Good Manufacturing Practices (GMP) and regulatory guidelines, within budgetary requirements. This includes all current product lines and new production introductions. The candidate must be a skilled negotiator with experience in the manufacturing/pharmaceutical industry. Responsibilities will include: * Source, identify and manage suppliers/partners for raw materials, packaging materials, while assessing ability to meet quality, service and delivery requirements. * Source, identify and manage suppliers/partners for goods and services, as required by the organisation. * Negotiate optimum price levels for raw and packaging materials. * Develop annual sourcing plans reflecting year-over-year cost reductions, quality enhancements and continuous improvement. * Define optimal sourcing policies regarding inventory management and replenishment frequencies. * Carry out annual supplier reviews with QAM. * Lead the way in managing obsolete and slow-moving stock, in the most profitable manner. * Monitor and drive KPIs including, process improvement, cost reduction, capacity planning and delivery. * Manage the artwork process for packaging and marketing materials, to meet internal and client requirements. * Identify and lead internal project teams as and when required. * Integral link between Local organisation Ireland and the parent Group, reporting as necessary. * Drive the highest level of teamwork, compliance and customer satisfaction at an optimum cost. Education, Experience and Additional Skills. * 3rd level qualification in business or supply chain. * Minimum 4-5 years relevant industry experience. * Experience of production capacity and constraints. * Experience of a continuous improvement programmes. * Excellent negotiation and influencing skills. * Excellent teamwork/interpersonal skills. * Motivation to drive continuous improvement & cost reduction processes. Our client is an equal opportunities employer.


AVerMedia Technologies, Inc., the preeminent name in digital multimedia technology, today announced the availability of GC553. The live gamer ULTRA provides a solution for laptop users and the streamers, allowing ultra-low latency capture on USB 3.1. With its high frame rate capture ability of up to 120 fps for Full HD signals, the LGU positions is the first external game capture device in the market to bring super smooth recordings.
Multi-Mode of GC553 helps to make broadcast more versatile and tools like, Scenes, Source Overlay, Chroma Key, Live Editing and many more are to make much more impressive video streams and recording and elevating video sessions to new heights. The advance bundled software 'CyberLink PowerDirector15' allows editing videos. With a powerful silent built-in fan, always keeps device cool in the heat of battles. The external Live Gamer ULTRA provides a solution for laptop users and streamers on the go, allowing ultra-low latency capture over USB 3.1.
It also supports HDR pass through, allowing gamers to play and view HDR while recording in standard 4K and its onboard video processing helps lighten the computer's CPU load.
GC553 also include AVerMedia's proprietary RECentral software that supports simultaneous streaming to Twitch, YouTube and Mixer from a single app, and Cyber Link's Power Director 15 video editing software, which supports 4K video.
GC533 comes with the MRP of Rs 31,000 carrying a warranty of 2 years. The product is available immediately through Tirupati Enterprises in India.


text:
Q: Como recorrer un JSON que retorna GraphQl estoy empezando a aprender javascript, y tengo un servidor con GraphQl, cuando realizo una consulta, me retorna un JSON, pero no he sabido como recorrerlo.
Esto es lo que me retorna
{
"data": {
   "getAllRequests": [
      {
         "id": 11
      },
      {
         "id": 0
      }
   ]
 }
}

Ahora bien puedo colocar directamente la ruta si quiero obtener algo, así
resData.data.getAllRequests[0].id

Pero aun no se como puedo conseguir por medio de un for que recorra todos los id, y me los muestre. He intentado lo siguiente:
for(var i in resData.data.getAllRequests){
            for(var j in resData.data.getAllRequests[i]){

            }
        }

Pero no me retorna exactamente el id.

A: Para recorrer el objeto tienes varias opciones, sin embargo lo que realmente deseas es recorrer el Array que viene en el atributo getAllRequests de tu objeto data.
PROBLEMA
Se desea recorrer un Array que contiene elementos (objetos) JavaScript. El Array es el valor de un atributo en un objeto que se recibe desde GraphQL.
SOLUCIÓN
Dado que el objeto que recibes tiene una estructura conocida, podemos recorrer su estructura y extraer la información relevante usando un bucle for, ya que es lo más adecuado según el planteamiento del problema.
En el código que pones de ejemplo en tu pregunta, veo que usas un bucle for...in, sin embargo, de acuerdo a la documentación de dicho bucle, este se usa para recorrer objetos, y en tu caso tu deseas recorrer un Array.
Para lograrlo usaremos el bucle for...of.

const obj = {
  "data": {
    "getAllRequests": [
      {
        "id": 11
      },
      {
        "id": 0
      },
      {
        "id": 3
      }
    ]
  }
}

const arr = obj.data.getAllRequests; // <= éste atributo contiene un array

for(let element of arr) {
  console.log('id: ', element.id); // <= cada elemento del array es un objeto
}

A veces tendemos a confundir la terminología y por lo tanto es común este tipo de errores.
Como GraphQL nos devuelve un objeto, nos vemos tentados usar el bucle for...in para recorrer cualquier elemento de nuestro objeto. Sin embargo, en este caso, el atributo gatAllRequests contiene como valor un Array, cuyos elementos son objetos JavaScript.
Otra forma de recorrer un Array es el método forEach(), la diferencia está en que el método forEach() iterará sobre todos los elementos del array sin que podamos evitarlo (a menos que lancemos una excepción), en cambio el bucle for...of podemos detenerlo con la sentencia break. Otra diferencia está en el hecho que el método forEach() ejecuta una función callback por cada elemento del array.
El código usando el método forEach() podría quedar así:

const obj = {
  "data": {
    "getAllRequests": [
      {
        "id": 11
      },
      {
        "id": 0
      },
      {
        "id": 3
      }
    ]
  }
}

const arr = obj.data.getAllRequests; // <= éste atributo contiene un array

arr.forEach((element) => {
    console.log('id: ', element.id);
});

Espero que esto aclare tu duda.

redpajama_set_name: RedPajamaStackExchange


An eclectic mix of urban blues rock with a little bit of 60's nostalgia thrown in.
11 MP3 Songs in this album (33:40) !
People who are interested in Bob Seeger John Fogerty Roy Orbison should consider this download.
An accomplished bassist/guitarist and singer/songwriter whose live music career spans 3 decades, he currently is not performing but prefers to spend his time writing and recording new material.
Lynn is owner of Marmaton Music Co. a BMI affiliate publisher and Clear Creek Records, his own in house label and has developed a passion for blues oriented rock. With a background that runs from The Rolling Stones, to The Beatles and Doors all the way through The Bodeans and Willie Nelson his songs are a mixture of Blues and Blues Rock.


Prezentare Every four years Americans elect a new Chief Executive. Our choice of the 2016 President will affect the future of the United States and the world for the rest of the 21st century.
Have fun with the facts, trivia, puzzles and more.


text:
FLYINGT
The first MB&F Machine dedicated to women
Even the most unpredictable and audaciously led lives, when observed from a distance, form patterns and fall into cycles. This is a fundamental truth that underlies all human existence, whether individual or collective. For Maximilian Büsser and MB&F, creative energy comes in seven-year cycles. It was in MB&F's seventh year that the Legacy Machine collection was born, that the first M.A.D.Gallery opened in Geneva and opened the door to the MB&F co-creations. The 14th year of MB&F unveils a new avenue of horological exploration, an evolution of creative horizons for founder and company alike.
Presenting Legacy Machine FlyingT, the first of MB&F's three-dimensional horological art pieces dedicated to women.
A round case in white gold, with a steeply cambered bezel and slender, elongated lugs, is fully set with diamonds. A high, extravagantly convex dome of sapphire crystal rises from the bezel. Beneath the dome, a subtly curved dial plate, liquidly black with layers of stretched lacquer or glittering with blazing white diamonds.
An asymmetric ventricular opening in the dial plate frames the heart of the LM FlyingT engine – a cinematic flying tourbillon that beats at a serene rate of 2.5Hz (18,000vph). The tourbillon projects high above the rest of the engine, a kinetic, dynamic column that stops just short of the apex of the sapphire crystal dome. Affixed to the top of the upper tourbillon cage is a single large diamond that rotates simultaneously with the flying tourbillon, emitting the fiery brilliance of the very best quality stones.
At the 7 o'clock position – another reference to the numeric theme that runs throughout LM FlyingT – is a dial of black or white lacquer that displays the hours and minutes with a pair of elegant serpentine hands in blued gold. The dial is inclined at a 50° tilt so that the time can be read only by the wearer, an intimate communication that highlights the personal nature of LM FlyingT.
On the reverse, the automatic winding rotor takes the shape of a three-dimensional red gold sun with sculpted rays, providing LM FlyingT with four days of power reserve.
The design of Legacy Machine FlyingT is replete with associations drawn from the feminine and maternal influences in Maximilian Büsser's life. Says Büsser, "I wanted LM FlyingT to possess the epitome of femininity as reflected by the women in my life, particularly my mother. It had to combine supreme elegance with tremendous vitality. The column-like structure of the flying tourbillon was very important to me as I felt very strongly that women form the pillar of humanity. At the same time, there is another layer of meaning coming in from the sun-shaped rotor, which incorporates the element of life-giving, a source of sustenance which we gravitate towards and around."
Legacy Machine FlyingT launches in three editions, all in white gold and set with diamonds:
– black lacquer dial plate with case set with brilliant-cut diamonds;
– dial plate and case both fully set with brilliant-cut diamonds;
– dial plate and case both fully set with baguette-cut diamonds.


Breathe through your nose while snorkeling. Good bye jaw discomfort. Good bye difficult breathing and goodbye water in your snorkel. For good.
Huge field of vision and no more fogging.
Aria also is the only full face snorkeling mask that offers a wide range of accessories!
This mask is not intended for free diving although swimming submerged is possible at shallow depths.
Goodbye jaw discomfort, goodbye difficult breathing and goodbye water in your snorkel. For good. With Aria you breathe through your nose, while the dry top prevents water from getting in the snorkel. Now your mouth is free to say: WOW!
No more water getting in your snorkel, you don't have to clear it if you go underwater, and no water gets in if there's surf, splash or such. Just breathe comfortably and enjoy the ride.
Normally, by taking the measure from your nose bridge, to the bottom of your chin, you can size correctly the mask that works best for you.
Aria's also equipped with a dock for sport cameras – you can record and relive your experience any time you want. Just take it out and jump in.


My final project is a continuation of my experiments in the event project, which is a tool to computationally sort images into sequence based on their common features.
3) Sorting, ordering a set of images into sequence using comparison function.
Last time, the algorithm used in each module is fixed. For the final project, I devised several alternative algorithms for each module, so the process can be customized into different pipelines suited for a variety of datasets.
I only had the time to sort one dataset (horses) for my event project since processing time take more than 24 hours. So the first priority is to speed it up.
I designed several numpy filters applied on images as matrices to replace pixel by pixel processing. This made the comparison part of my program run around 200 times faster. However the image segmentation is still relatively slow because the images are uploaded and then downloaded from the online image segmentation demo.
Now that the program runs much faster, I had the chance to apply it on a variety of datasets.
My previous algorithm compares two images based on the outline of the object of interest. However, this is not always desired for every dataset. For example, the outlines of people's heads might be similar, yet their facial features and expressions can be vastly different so if made adjacent frames, it wouldn't look smooth.
From peer feedback, I realized that people are not only interested in the structural similarity between objects in two images, but also other properties such as date of creation, color, and other dataset-specific information.
However, the major problem sorting images using these methods is that the result will look extremely choppy and confusing. Therefore, I thought about the possibility of "morphing" between two images.
If I am able to find a method to break a region of image into several triangles, and perform an affine transformation of the triangles from one image to another, slowly transitioning the color at the same time, I can achieve a smooth morphing effect between the images.
In order to do an affine transformation on a triangle from original transformation to target transformation, I first recursively divide the original triangle into many tiny triangles using a method similar to the Sierpinski's fractal, but instead of leaving the middle triangles untouched my algorithm operates on all the triangles.
Since facial landmark detection already provided the key points, a face can now easily be divided into triangles and be transformed into another face. Below is the same portraits from MET sorted by chronological order, morphed.
The problem now is to find a consistent method to break other things that are not faces into many triangles. Currently I'm still trying to think of a way.
An idea suddenly came into my mind, what if I re-sort some images that are already sorted? If I convert a video into frames, shuffle them, and sort them using my own algorithm into a video, what would I get? It will probably still be smoothly animated, but what does the animation mean? Does it tell a new story or give a new insight into the event? The result will be what the algorithm "think" the event "should" be like, without understanding the actual event. I find this to be an exciting way to abuse my own tool.
I ran the algorithm on my own meaningless dance. It transformed into another, quite different, meaningless dance. I wonder what happens if I do the same on a professional dancer performing a familiar dance.
When I tried to run the image segmentation on ballet dancers, the algorithm believed them to be half-cow, half-human, armless monsters. I guess it hasn't been trained on sufficient amount of costumed people to recognize them.
So I had to write my own background subtraction algorithm.
In the original video, the background is the same, yet the camera angle is constantly moving, so I couldn't simply median all the frames and subtract from it. I also couldn't use similar methods because the dancer is always right at the middle of the frame, and the average/median/maximum of the frames will all have a white blob in the center, which is not helpful.
Therefore I used a similar method described in my event post, which is for each frame, learn the background from a certain region, and subtract the pixels that resembles this sample surrounding the object of interest.
I sample the leftmost section of each frame, where the dancer has never been to, and horizontally stack this section into the width of the frame, and subtract the original from this estimated background.
Combined with erode/dialect and other openCV tricks, the it worked pretty well. This method is not only useful for ballets, as it's a common situation in many datasets to have relatively uniform backgrounds yet complicated foregrounds.


Doug Manhart grew up on a farm, moved away for college and jobs, and came back five years ago. He feeds cattle through the winter and raises wheat and corn. Doug is determined to do what is necessary to prevent having to return to tillage to control weeds.
Soil health is important because it takes tremendous time to build and minutes to destroy. To me, soil health may be a way to get more out of your inputs in tough economic times.
Soil health is a priority because it is the foundation of our farm. It appears to be a variable that improves your bottom line in all conditions and circumstances.
Challenges include resistant weed pressure and how to measure if we are making progress. I feel we are on the right track, but do I know for sure? Habits. The "that's what we did last year" mentality. Uncertainty about new and seemingly unproven methods.


There are four main types of PMS, which come under the following categories. This is to be used as a guideline only, as there is usually more than one set of symptoms experienced by most women.
The following tips for PMS are generalised and are suitable for all types of PMS disorders. Please note that severe hormonal imbalances may need to be rectified with the use of additional supplementation.
Are you after a snack or breakfast idea to help ease the perils of PMS? Look no further than the PMS Smoothie in the week prior to and during your menstrual cycle. It contains phytoestrogens, protein and fibre and is packed with nutrients to assist hormonal balance, mood and blood sugar control around 'that time of the month'. And it has just enough of cacao to satisfy a chocolate craving.
Place in a high-speed blender and process on 'high' until smooth.
N.B: Cacao does contain a small amount of caffeine, which you can omit and swap for carob powder if desired.
1. Sarris, J, Wardle, J (2010) Clinical naturopathy, Churchhill Livingstone, Australia. Page 350.