<commit_before>export { TwinArray };

import * as TensorPlaceholder from "../TensorPlaceholder.js";
import { Root } from "./Operation_Base.js";

/**
 * An array of operations. Every time appending operation, one or parallel twin operations could be appended.
 *
 * No matter how many sub operations (even no sub operation) are appended, this TwinArray's .output0 and .output1 (tensor
 * placeholder) are always .endingDummyOperation.output0 and .endingDummyOperation.output1 in fact. The .endingInput0
 * and endingInput1 are always .endingDummyOperation.input0 and .endingDummyOperation.input1 in fact. This could
 * simplify the decision of what tensor placeholders should be used as the next (appended) operation's input.
 *
 *
 * Note: The .outputTensorCount is determined when constructor is called. No matter what kind of operation is appended
 * (even if the is no operation is appended), the outputTensorCount will not be changed.
 *
 *
 *
 * @member {TensorPlaceholder.Base} endingInput0
 *   The last sub operation's output0 (also .endingDummyOperation.input0). It could be used as input of next appended sub operation.
 *
 * @member {TensorPlaceholder.Base} endingInput1
 *   The last sub operation's output1 (also .endingDummyOperation.input1). It could be used as input of next appended sub operation.
 *
 * @member {number} tensorWeightCountExtracted
 *   The sum of all sub operations' tensorWeightCountExtracted.
 *
 * @member {number} tensorWeightCountTotal
 *   The sum of all sub operations' tensorWeightCountTotal.
 *
 * @member {function} apply
 *   This is a data member pointer to a function. It processes this.input0.realTensor (and this.input1.realTensor) as inputTensors. It
 * puts to this.output0.realTensor (and this.output1.realTensor) as outputTensor. In fact, its calls .apply of every sub operation
 * and .endingDummyOperation.
 *
 * @see Operation.Root
 */
class TwinArray extends Root {

  /**
   *
   */
  constructor( inputTensorPlaceholder0, inputTensorPlaceholder1, outputTensorCount, ...restArgs ) {

    // Note: The real output TensorPlacehoder will be created later as final operation outputs.
    super( inputTensorPlaceholder0, inputTensorPlaceholder1, 0, ...restArgs );

    // In order to handle keep-input-flag correctly (even if no sub operation at all), an ending dummy operation is used.
    {
      this.endingDummyOperation = new Root( inputTensorPlaceholder0, inputTensorPlaceholder1, outputTensorCount );

      // The ending dummy operation's output will be the output of this operation array.
      {
        if ( this.endingDummyOperation.output0 )
          this.output0 = this.endingDummyOperation.output0;
        if ( this.endingDummyOperation.output1 )
          this.output1 = this.endingDummyOperation.output1;
      }
    }

    this.operationArray = new Array();

    this.bKeepInputTensor0 = false; // Default is destroy0 and destroy1;
    this.bKeepInputTensor1 = false;
    TwinArray.alwaysKeepSet_collect.call( this ); // Re-collect TensorPlaceholders which are requested to keep their tensors.
    TwinArray.setKeepInputTensor_by_this_operationArray_endingDummyOperation_alwaysKeepSet.call( this );

    TwinArray.setup_apply_loop.call( this );

    // For reducing memory re-allocation.
    //
    // When one or twin operations are appended, the newly appende operations' output tensor placeholders will be collected here
    // temporarily. And then they will be assigned as endingDummyOperation's input tensor placeholder.
    //
    this.tempLastOutputTensorPlaceholderArray = new Array();
  }

  /**
   * @override
   */
  disposeTensors() {

    {
      for ( let i = 0; i < this.operationArray.length; ++i ) {
        let operation = this.operationArray[ i ];
        operation.disposeTensors();
      }
      this.operationArray.length = 0;
    }

    // Since there is no sub operation, short-circuit to the original inputs.
    TwinArray.set_endingInput0_endingInput1.call( this, this.input0, this.input1 );

    super.disposeTensors();
  }

  /**
   * Release all ScaleBoundsArray (inside tensor placeholder) except this.inputX and this.outputX
   *
   * This could reduce memory footprint by releasing unused scale bounds array.
   */
  dispose_intermediate_ScaleBoundsArray() {
    for ( let i = 0; i < this.operationArray.length; ++i ) {
      let operation = this.operationArray[ i ];
      if ( !this.is_inputs_outputs_byTensorPlaceholder( operation.input0 ) )
        operation.input0.scaleBoundsArray = null;
      if ( !this.is_inputs_outputs_byTensorPlaceholder( operation.input1 ) )
        operation.input1.scaleBoundsArray = null;
      if ( !this.is_inputs_outputs_byTensorPlaceholder( operation.output0 ) )
        operation.output0.scaleBoundsArray = null;
      if ( !this.is_inputs_outputs_byTensorPlaceholder( operation.output1 ) )
        operation.output1.scaleBoundsArray = null;
    }
  }

  /**
   * @override
   */
  setKeepInputTensor( bKeepInputTensor0, bKeepInputTensor1 ) {

    if ( ( this.bKeepInputTensor0 == bKeepInputTensor0 ) && ( this.bKeepInputTensor1 == bKeepInputTensor1 ) )
      return; // Do nothing because no changed.

    this.bKeepInputTensor0 = bKeepInputTensor0;
    this.bKeepInputTensor1 = bKeepInputTensor1;
    TwinArray.alwaysKeepSet_collect.call( this );
    TwinArray.setKeepInputTensor_by_this_operationArray_endingDummyOperation_alwaysKeepSet.call( this );
  }

  /**
   * Call every sub operation's and endingDummyOperation's setKeepInputTensor_IfNotFinalOperation_Or_In() with this.alwaysKeepSet.
   */
  static setKeepInputTensor_by_this_operationArray_endingDummyOperation_alwaysKeepSet() {

    // 1. Every input tensors' final operation is responsible for releasing the tensor (except the input tensors which are requested
    //    to be kept (i.e. inside alwaysKeepSet)).
    //
    for ( let i = 0; i < this.operationArray.length; ++i ) {
      let operation = this.operationArray[ i ];
      operation.setKeepInputTensor_IfNotFinalOperation_Or_In( this.alwaysKeepSet );
    }

    // 2.
    this.endingDummyOperation.setKeepInputTensor_IfNotFinalOperation_Or_In( this.alwaysKeepSet );
  }

//!!! (2022/06/06 Remarked) .init() should be done by caller (not by TwinArray).
//   /**
//    * @param {Base} this
//    *   The Block.Base object whose .byteOffsetEnd might be updated.
//    *
//    * @param {Operation.Base} operationObject
//    *   The operation object to be initialized.
//    *
//    * @param {Array} initArgs
//    *   The arguments to be passed to the init() method of operation object.
//    *   - If null, the operation object's init() will not be called. Usually, this means the operation object needs not extract any weights.
//    *   - If the .init() is called and returns false, this method will failed and return null.
//    *   - If the .init() is called and returns true, this method will update this.byteOffsetEnd.
//    *
//    * @return {boolean} If success, return true. If failed, return false.
//    */
//   static operation__update_byteOffsetEnd_if_init_ok( operationObject, initArgs ) {
//
//     // 1. Intialize.
//     if ( initArgs ) {
//       if ( !operationObject.init.apply( operationObject, initArgs ) )
//         return false;  // e.g. input array does not have enough data.
//
//       this.byteOffsetEnd = operationObject.byteOffsetEnd;
//
//     // Otherwise (i.e. no initArgs), do not call operationObject.init() and do not update this.byteOffsetEnd
//     }
//
//     // 2. Adjust keep-input-tensor flags.
//     //
//     // The previous final operation (of input tensor placeholders) is no longer its final operation.
//     // The newly created operation becomes the final operation of its input.
//     //
//     operationObject.setKeepInputTensor__input0_finalOperationOld__input1_finalOperationOld__this__IfNotFinalOperation_Or_In( this.alwaysKeepSet );
//     return true;
//   }

  /**
   * Append one or twin (i.e. two operation in parallel) operation(s) into this.operationArray[].
   *
   * All of the newly appended operations' .outputX TensorPlaceholders will be assigned as .endingDummyOperation.inputX (i.e.
   * .endingInputX) with some rules:
   *
   *   - If a .endingDummyOperation is no longer the final operation of .endingDummyOperation.inputX, the .endingDummyOperation.inputX
   *       will be viewed as empty solt which could be assigned by newly appended operations' .outputX.
   *
   *   - However, because there are only two slots (i.e. .endingDummyOperation.input0 and .endingDummyOperation.input1) at most,
   *       there are only two operationX.outputY could be assigned to them.
   *
   *   - If there too many operationX.outputY needs to be handled, it will be asserted failed.
   *
   *
   * @param {Operation.Base} operation0  The 1st operation object to be appended into this.operationArray[].
   * @param {Operation.Base} operation1  The 2nd operation object. If null, there will be no 2nd operation.
   */
  operation_append( operation0, operation1 ) {

    // 1. Adjust keep-input-tensor flags, and put into queue.
    //
    // The previous final operation (of input tensor placeholders) is no longer its final operation.
    // The newly created operation becomes the final operation of its input.
    //
    if ( operation0 ) {
      operation0.setKeepInputTensor__input0_finalOperationOld__input1_finalOperationOld__this__IfNotFinalOperation_Or_In( this.alwaysKeepSet );
      this.operationArray.push( operation0 );
    }

    if ( operation1 ) {
      operation1.setKeepInputTensor__input0_finalOperationOld__input1_finalOperationOld__this__IfNotFinalOperation_Or_In( this.alwaysKeepSet );
      this.operationArray.push( operation1 );
    }

    // 2. Determine whether give up or keep current .endingInputX
    //
    // No matter ( .endingInputX does not exist ) or ( .endingInputX exists but .endingDummyOperation is no longer its final
    // operation ), .endingInputX will become an empty slot which could handle newly generated output tensor placeholders from
    // newly appended operations.
    //
    // Q: Why .endingDummyOperation should give it up when .endingDummyOperation is no longer its input tensor placeholder's
    //    final operation (i.e. the newly created and appended operation becomes its final operation)?
    // A: So that .endingDummyOperation will not use a disposed tensor.
    //
    let endingInput0_new, endingInput1_new;
    {
      if ( this.endingDummyOperation.input0?.finalOperation == this.endingDummyOperation )
        endingInput0_new = this.endingDummyOperation.input0; // Continue to handle it Since it still is its final operation.
      else
        endingInput0_new = null;

      if ( this.endingDummyOperation.input1?.finalOperation == this.endingDummyOperation )
        endingInput1_new = this.endingDummyOperation.input1; // Continue to handle it Since it still is its final operation.
      else
        endingInput1_new = null;
    }

    // 3. Adjust .endingInputX

    // 3.1 Collect output tensor placeholders in reverse order (so that they will be popped in forward order).
    {
      this.tempLastOutputTensorPlaceholderArray.length = 0;

      if ( operation1 ) {
        if ( operation1.output1 )
          this.tempLastOutputTensorPlaceholderArray.push( operation1.output1 );
        if ( operation1.output0 )
          this.tempLastOutputTensorPlaceholderArray.push( operation1.output0 );
      }

      if ( operation0 ) {
        if ( operation0.output1 )
          this.tempLastOutputTensorPlaceholderArray.push( operation0.output1 );
        if ( operation0.output0 )
          this.tempLastOutputTensorPlaceholderArray.push( operation0.output0 );
      }
    }

    // 3.2 Assign newly generated output tensor placeholders (from newly appended operations) to empty input slot of .endingDummyOperation.
    {
      if ( ( this.tempLastOutputTensorPlaceholderArray.length > 0 ) && ( endingInput0_new == null ) )
        endingInput0_new = this.tempLastOutputTensorPlaceholderArray.pop();

      if ( ( this.tempLastOutputTensorPlaceholderArray.length > 0 ) && ( endingInput1_new == null ) )
        endingInput1_new = this.tempLastOutputTensorPlaceholderArray.pop();

      // At most, two outputs could be handled.
      tf.util.assert( ( this.tempLastOutputTensorPlaceholderArray.length == 0 ),
        `Operation.TwinArray.operation_append(): `
          + `The appended operations have too many output TensorPlaceholders. There are not enough .endingInputX could be used.`
      );
    }

    // 3.3 Confirm the new endingInputX.
    TwinArray.set_endingInput0_endingInput1.call( this, endingInput0_new, endingInput1_new );
  }

  get endingInput0() {
    return this.endingDummyOperation.input0;
  }

  get endingInput1() {
    return this.endingDummyOperation.input1;
  }

  get tensorWeightCountExtracted() {
    let sum = 0;
    for ( let i = 0; i < this.operationArray.length; ++i ) {
      let operation = this.operationArray[ i ];
      sum += operation.tensorWeightCountExtracted;
    }
    return sum;
  }

  get tensorWeightCountTotal() {
    let sum = 0;
    for ( let i = 0; i < this.operationArray.length; ++i ) {
      let operation = this.operationArray[ i ];
      sum += operation.tensorWeightCountTotal;
    }
    return sum;
  }

  /** Re-collect TensorPlaceholders into this.alwaysKeepSet if some input tensors are requested to be kept after .apply().
   */
  static alwaysKeepSet_collect() {
    let bShouldbKeepInputTensor0 = ( this.input0 && this.bKeepInputTensor0 );
    let bShouldbKeepInputTensor1 = ( this.input1 && this.bKeepInputTensor1 );
    let bShouldbKeepInputTensor = ( bShouldbKeepInputTensor0 || bShouldbKeepInputTensor1 );

    if ( bShouldbKeepInputTensor ) { // 1. Some inputs should be kept.

      // 1.1
      if ( this.alwaysKeepSet )
        this.alwaysKeepSet.clear();
      else
        this.alwaysKeepSet = new Set(); // Create container only if it is indeed necessary.

      if ( bShouldbKeepInputTensor0 ) // 1.2
        this.alwaysKeepSet.add( this.input0 );

      if ( bShouldbKeepInputTensor1 ) // 1.3
        this.alwaysKeepSet.add( this.input1 );

    } else { // 2. No inputs should be kept.

      // 2.1 Empty the container if the container exists.
      if ( this.alwaysKeepSet )
        this.alwaysKeepSet.clear();

      // 2.2 If the container does not exist, do nothing instead.
    }
  }

  /**
   *
   * @param {TensorPlaceholder.Base} endingInput0  The tensor placeholder to become .endingDummyOperation.input0.
   * @param {TensorPlaceholder.Base} endingInput1  The tensor placeholder to become .endingDummyOperation.input1.
   */
  static set_endingInput0_endingInput1( endingInput0, endingInput1 ) {
    TwinArray.set_inputTensorPlaceholder0_inputTensorPlaceholder1.call( this.endingDummyOperation, endingInput0, endingInput1 );
    this.endingDummyOperation.setKeepInputTensor__input0_finalOperationOld__input1_finalOperationOld__this__IfNotFinalOperation_Or_In( this.alwaysKeepSet );
  }

  /**
   * Adjust .apply data member according to calling .apply_operationArray_endingDummyOperation().
   */
  static setup_apply_loop() {
    this.apply = TwinArray.apply_operationArray_endingDummyOperation;
  }

  /**
   * Calling every sub operations' and .endingDummyOperation's .apply()
   */
  static apply_operationArray_endingDummyOperation() {
    for ( let i = 0; i < this.operationArray.length; ++i ) {
      let operation = this.operationArray[ i ];
      operation.apply();
    }
    this.endingDummyOperation.apply();
  }

}
<commit_msg>Update Operation_TwinArray.js<commit_after>export { TwinArray };

import * as TensorPlaceholder from "../TensorPlaceholder.js";
import { Root } from "./Operation_Base.js";

/**
 * An array of operations. Every time appending operation, one or parallel twin operations could be appended.
 *
 * No matter how many sub operations (even no sub operation) are appended, this TwinArray's .output0 and .output1 (tensor
 * placeholder) are always .endingDummyOperation.output0 and .endingDummyOperation.output1 in fact. The .endingInput0
 * and endingInput1 are always .endingDummyOperation.input0 and .endingDummyOperation.input1 in fact. This could
 * simplify the decision of what tensor placeholders should be used as the next (appended) operation's input.
 *
 *
 * Note: The .outputTensorCount is determined when constructor is called. No matter what kind of operation is appended
 * (even if the is no operation is appended), the outputTensorCount will not be changed.
 *
 *
 *
 * @member {TensorPlaceholder.Base} endingInput0
 *   The last sub operation's output0 (also .endingDummyOperation.input0). It could be used as input of next appended sub operation.
 *
 * @member {TensorPlaceholder.Base} endingInput1
 *   The last sub operation's output1 (also .endingDummyOperation.input1). It could be used as input of next appended sub operation.
 *
 * @member {number} tensorWeightCountExtracted
 *   The sum of all sub operations' tensorWeightCountExtracted.
 *
 * @member {number} tensorWeightCountTotal
 *   The sum of all sub operations' tensorWeightCountTotal.
 *
 * @member {function} apply
 *   This is a data member pointer to a function. It processes this.input0.realTensor (and this.input1.realTensor) as inputTensors. It
 * puts to this.output0.realTensor (and this.output1.realTensor) as outputTensor. In fact, its calls .apply of every sub operation
 * and .endingDummyOperation.
 *
 * @see Operation.Root
 */
class TwinArray extends Root {

  /**
   *
   */
  constructor( inputTensorPlaceholder0, inputTensorPlaceholder1, outputTensorCount, ...restArgs ) {

    // Note: The real output TensorPlacehoder will be created later as final operation outputs.
    super( inputTensorPlaceholder0, inputTensorPlaceholder1, 0, ...restArgs );

    // In order to handle keep-input-flag correctly (even if no sub operation at all), an ending dummy operation is used.
    {
      this.endingDummyOperation = new Root( inputTensorPlaceholder0, inputTensorPlaceholder1, outputTensorCount );

      // The ending dummy operation's output will be the output of this operation array.
      {
        if ( this.endingDummyOperation.output0 )
          this.output0 = this.endingDummyOperation.output0;
        if ( this.endingDummyOperation.output1 )
          this.output1 = this.endingDummyOperation.output1;
      }
    }

    this.operationArray = new Array();

    this.bKeepInputTensor0 = false; // Default is destroy0 and destroy1;
    this.bKeepInputTensor1 = false;
    TwinArray.alwaysKeepSet_collect.call( this ); // Re-collect TensorPlaceholders which are requested to keep their tensors.
    TwinArray.setKeepInputTensor_by_this_operationArray_endingDummyOperation_alwaysKeepSet.call( this );

    TwinArray.setup_apply_loop.call( this );

    // For reducing memory re-allocation.
    //
    // When one or twin operations are appended, the newly appende operations' output tensor placeholders will be collected here
    // temporarily. And then they will be assigned as endingDummyOperation's input tensor placeholder.
    //
    this.tempLastOutputTensorPlaceholderArray = new Array();
  }

  /**
   * @override
   */
  disposeTensors() {

    {
      for ( let i = 0; i < this.operationArray.length; ++i ) {
        let operation = this.operationArray[ i ];
        operation.disposeTensors();
      }
      this.operationArray.length = 0;
    }

    // Since there is no sub operation, short-circuit to the original inputs.
    TwinArray.set_endingInput0_endingInput1.call( this, this.input0, this.input1 );

    super.disposeTensors();
  }

  /**
   * Release all ScaleBoundsArray (inside tensor placeholder) except this.input0, this.input1, this.output0, this.output1
   *
   * This could reduce memory footprint by releasing unused scale bounds array.
   */
  dispose_intermediate_ScaleBoundsArray() {
    for ( let i = 0; i < this.operationArray.length; ++i ) {
      let operation = this.operationArray[ i ];
      if ( ( operation.input0 ) && !( this.is_inputs_outputs_byTensorPlaceholder( operation.input0 ) ) )
        operation.input0.scaleBoundsArray = null;
      if ( ( operation.input1 ) && !( this.is_inputs_outputs_byTensorPlaceholder( operation.input1 ) ) )
        operation.input1.scaleBoundsArray = null;
      if ( ( operation.output0 ) && !( this.is_inputs_outputs_byTensorPlaceholder( operation.output0 ) ) )
        operation.output0.scaleBoundsArray = null;
      if ( ( operation.output1 ) && !( this.is_inputs_outputs_byTensorPlaceholder( operation.output1 ) ) )
        operation.output1.scaleBoundsArray = null;
    }
  }

  /**
   * @override
   */
  setKeepInputTensor( bKeepInputTensor0, bKeepInputTensor1 ) {

    if ( ( this.bKeepInputTensor0 == bKeepInputTensor0 ) && ( this.bKeepInputTensor1 == bKeepInputTensor1 ) )
      return; // Do nothing because no changed.

    this.bKeepInputTensor0 = bKeepInputTensor0;
    this.bKeepInputTensor1 = bKeepInputTensor1;
    TwinArray.alwaysKeepSet_collect.call( this );
    TwinArray.setKeepInputTensor_by_this_operationArray_endingDummyOperation_alwaysKeepSet.call( this );
  }

  /**
   * Call every sub operation's and endingDummyOperation's setKeepInputTensor_IfNotFinalOperation_Or_In() with this.alwaysKeepSet.
   */
  static setKeepInputTensor_by_this_operationArray_endingDummyOperation_alwaysKeepSet() {

    // 1. Every input tensors' final operation is responsible for releasing the tensor (except the input tensors which are requested
    //    to be kept (i.e. inside alwaysKeepSet)).
    //
    for ( let i = 0; i < this.operationArray.length; ++i ) {
      let operation = this.operationArray[ i ];
      operation.setKeepInputTensor_IfNotFinalOperation_Or_In( this.alwaysKeepSet );
    }

    // 2.
    this.endingDummyOperation.setKeepInputTensor_IfNotFinalOperation_Or_In( this.alwaysKeepSet );
  }

//!!! (2022/06/06 Remarked) .init() should be done by caller (not by TwinArray).
//   /**
//    * @param {Base} this
//    *   The Block.Base object whose .byteOffsetEnd might be updated.
//    *
//    * @param {Operation.Base} operationObject
//    *   The operation object to be initialized.
//    *
//    * @param {Array} initArgs
//    *   The arguments to be passed to the init() method of operation object.
//    *   - If null, the operation object's init() will not be called. Usually, this means the operation object needs not extract any weights.
//    *   - If the .init() is called and returns false, this method will failed and return null.
//    *   - If the .init() is called and returns true, this method will update this.byteOffsetEnd.
//    *
//    * @return {boolean} If success, return true. If failed, return false.
//    */
//   static operation__update_byteOffsetEnd_if_init_ok( operationObject, initArgs ) {
//
//     // 1. Intialize.
//     if ( initArgs ) {
//       if ( !operationObject.init.apply( operationObject, initArgs ) )
//         return false;  // e.g. input array does not have enough data.
//
//       this.byteOffsetEnd = operationObject.byteOffsetEnd;
//
//     // Otherwise (i.e. no initArgs), do not call operationObject.init() and do not update this.byteOffsetEnd
//     }
//
//     // 2. Adjust keep-input-tensor flags.
//     //
//     // The previous final operation (of input tensor placeholders) is no longer its final operation.
//     // The newly created operation becomes the final operation of its input.
//     //
//     operationObject.setKeepInputTensor__input0_finalOperationOld__input1_finalOperationOld__this__IfNotFinalOperation_Or_In( this.alwaysKeepSet );
//     return true;
//   }

  /**
   * Append one or twin (i.e. two operation in parallel) operation(s) into this.operationArray[].
   *
   * All of the newly appended operations' .outputX TensorPlaceholders will be assigned as .endingDummyOperation.inputX (i.e.
   * .endingInputX) with some rules:
   *
   *   - If a .endingDummyOperation is no longer the final operation of .endingDummyOperation.inputX, the .endingDummyOperation.inputX
   *       will be viewed as empty solt which could be assigned by newly appended operations' .outputX.
   *
   *   - However, because there are only two slots (i.e. .endingDummyOperation.input0 and .endingDummyOperation.input1) at most,
   *       there are only two operationX.outputY could be assigned to them.
   *
   *   - If there too many operationX.outputY needs to be handled, it will be asserted failed.
   *
   *
   * @param {Operation.Base} operation0  The 1st operation object to be appended into this.operationArray[].
   * @param {Operation.Base} operation1  The 2nd operation object. If null, there will be no 2nd operation.
   */
  operation_append( operation0, operation1 ) {

    // 1. Adjust keep-input-tensor flags, and put into queue.
    //
    // The previous final operation (of input tensor placeholders) is no longer its final operation.
    // The newly created operation becomes the final operation of its input.
    //
    if ( operation0 ) {
      operation0.setKeepInputTensor__input0_finalOperationOld__input1_finalOperationOld__this__IfNotFinalOperation_Or_In( this.alwaysKeepSet );
      this.operationArray.push( operation0 );
    }

    if ( operation1 ) {
      operation1.setKeepInputTensor__input0_finalOperationOld__input1_finalOperationOld__this__IfNotFinalOperation_Or_In( this.alwaysKeepSet );
      this.operationArray.push( operation1 );
    }

    // 2. Determine whether give up or keep current .endingInputX
    //
    // No matter ( .endingInputX does not exist ) or ( .endingInputX exists but .endingDummyOperation is no longer its final
    // operation ), .endingInputX will become an empty slot which could handle newly generated output tensor placeholders from
    // newly appended operations.
    //
    // Q: Why .endingDummyOperation should give it up when .endingDummyOperation is no longer its input tensor placeholder's
    //    final operation (i.e. the newly created and appended operation becomes its final operation)?
    // A: So that .endingDummyOperation will not use a disposed tensor.
    //
    let endingInput0_new, endingInput1_new;
    {
      if ( this.endingDummyOperation.input0?.finalOperation == this.endingDummyOperation )
        endingInput0_new = this.endingDummyOperation.input0; // Continue to handle it Since it still is its final operation.
      else
        endingInput0_new = null;

      if ( this.endingDummyOperation.input1?.finalOperation == this.endingDummyOperation )
        endingInput1_new = this.endingDummyOperation.input1; // Continue to handle it Since it still is its final operation.
      else
        endingInput1_new = null;
    }

    // 3. Adjust .endingInputX

    // 3.1 Collect output tensor placeholders in reverse order (so that they will be popped in forward order).
    {
      this.tempLastOutputTensorPlaceholderArray.length = 0;

      if ( operation1 ) {
        if ( operation1.output1 )
          this.tempLastOutputTensorPlaceholderArray.push( operation1.output1 );
        if ( operation1.output0 )
          this.tempLastOutputTensorPlaceholderArray.push( operation1.output0 );
      }

      if ( operation0 ) {
        if ( operation0.output1 )
          this.tempLastOutputTensorPlaceholderArray.push( operation0.output1 );
        if ( operation0.output0 )
          this.tempLastOutputTensorPlaceholderArray.push( operation0.output0 );
      }
    }

    // 3.2 Assign newly generated output tensor placeholders (from newly appended operations) to empty input slot of .endingDummyOperation.
    {
      if ( ( this.tempLastOutputTensorPlaceholderArray.length > 0 ) && ( endingInput0_new == null ) )
        endingInput0_new = this.tempLastOutputTensorPlaceholderArray.pop();

      if ( ( this.tempLastOutputTensorPlaceholderArray.length > 0 ) && ( endingInput1_new == null ) )
        endingInput1_new = this.tempLastOutputTensorPlaceholderArray.pop();

      // At most, two outputs could be handled.
      tf.util.assert( ( this.tempLastOutputTensorPlaceholderArray.length == 0 ),
        `Operation.TwinArray.operation_append(): `
          + `The appended operations have too many output TensorPlaceholders. There are not enough .endingInputX could be used.`
      );
    }

    // 3.3 Confirm the new endingInputX.
    TwinArray.set_endingInput0_endingInput1.call( this, endingInput0_new, endingInput1_new );
  }

  get endingInput0() {
    return this.endingDummyOperation.input0;
  }

  get endingInput1() {
    return this.endingDummyOperation.input1;
  }

  get tensorWeightCountExtracted() {
    let sum = 0;
    for ( let i = 0; i < this.operationArray.length; ++i ) {
      let operation = this.operationArray[ i ];
      sum += operation.tensorWeightCountExtracted;
    }
    return sum;
  }

  get tensorWeightCountTotal() {
    let sum = 0;
    for ( let i = 0; i < this.operationArray.length; ++i ) {
      let operation = this.operationArray[ i ];
      sum += operation.tensorWeightCountTotal;
    }
    return sum;
  }

  /** Re-collect TensorPlaceholders into this.alwaysKeepSet if some input tensors are requested to be kept after .apply().
   */
  static alwaysKeepSet_collect() {
    let bShouldbKeepInputTensor0 = ( this.input0 && this.bKeepInputTensor0 );
    let bShouldbKeepInputTensor1 = ( this.input1 && this.bKeepInputTensor1 );
    let bShouldbKeepInputTensor = ( bShouldbKeepInputTensor0 || bShouldbKeepInputTensor1 );

    if ( bShouldbKeepInputTensor ) { // 1. Some inputs should be kept.

      // 1.1
      if ( this.alwaysKeepSet )
        this.alwaysKeepSet.clear();
      else
        this.alwaysKeepSet = new Set(); // Create container only if it is indeed necessary.

      if ( bShouldbKeepInputTensor0 ) // 1.2
        this.alwaysKeepSet.add( this.input0 );

      if ( bShouldbKeepInputTensor1 ) // 1.3
        this.alwaysKeepSet.add( this.input1 );

    } else { // 2. No inputs should be kept.

      // 2.1 Empty the container if the container exists.
      if ( this.alwaysKeepSet )
        this.alwaysKeepSet.clear();

      // 2.2 If the container does not exist, do nothing instead.
    }
  }

  /**
   *
   * @param {TensorPlaceholder.Base} endingInput0  The tensor placeholder to become .endingDummyOperation.input0.
   * @param {TensorPlaceholder.Base} endingInput1  The tensor placeholder to become .endingDummyOperation.input1.
   */
  static set_endingInput0_endingInput1( endingInput0, endingInput1 ) {
    TwinArray.set_inputTensorPlaceholder0_inputTensorPlaceholder1.call( this.endingDummyOperation, endingInput0, endingInput1 );
    this.endingDummyOperation.setKeepInputTensor__input0_finalOperationOld__input1_finalOperationOld__this__IfNotFinalOperation_Or_In( this.alwaysKeepSet );
  }

  /**
   * Adjust .apply data member according to calling .apply_operationArray_endingDummyOperation().
   */
  static setup_apply_loop() {
    this.apply = TwinArray.apply_operationArray_endingDummyOperation;
  }

  /**
   * Calling every sub operations' and .endingDummyOperation's .apply()
   */
  static apply_operationArray_endingDummyOperation() {
    for ( let i = 0; i < this.operationArray.length; ++i ) {
      let operation = this.operationArray[ i ];
      operation.apply();
    }
    this.endingDummyOperation.apply();
  }

}
<|endoftext|>


<commit_before>"use strict";

var dbm;
var type;
var seed;
var fs = require("fs");
var path = require("path");
var Promise;

/**
  * We receive the dbmigrate dependency from dbmigrate initially.
  * This enables us to not have to rely on NODE_PATH.
  */
exports.setup = function(options, seedLink) {
  dbm = options.dbmigrate;
  type = dbm.dataType;
  seed = seedLink;
  Promise = options.Promise;
};

exports.up = function(db) {
  var filePath = path.join(__dirname, "sqls", "20170728133556-initial-up.sql");
  return new Promise(function(resolve, reject) {
    fs.readFile(filePath, { encoding: "utf-8" }, function(err, data) {
      if (err) return reject(err);
      console.log("received data: " + data);

      resolve(data);
    });
  }).then(function(data) {
    return db.runSql(data);
  });
};

exports.down = function(db) {
  var filePath = path.join(
    __dirname,
    "sqls",
    "20170728133556-initial-down.sql"
  );
  return new Promise(function(resolve, reject) {
    fs.readFile(filePath, { encoding: "utf-8" }, function(err, data) {
      if (err) return reject(err);
      console.log("received data: " + data);

      resolve(data);
    });
  }).then(function(data) {
    return db.runSql(data);
  });
};

exports._meta = {
  version: 1
};
<commit_msg>fixed prettify<commit_after>'use strict';

var dbm;
var type;
var seed;
var fs = require('fs');
var path = require('path');
var Promise;

/**
  * We receive the dbmigrate dependency from dbmigrate initially.
  * This enables us to not have to rely on NODE_PATH.
  */
exports.setup = function(options, seedLink) {
  dbm = options.dbmigrate;
  type = dbm.dataType;
  seed = seedLink;
  Promise = options.Promise;
};

exports.up = function(db) {
  var filePath = path.join(__dirname, 'sqls', '20170728133556-initial-up.sql');
  return new Promise(function(resolve, reject) {
    fs.readFile(filePath, { encoding: 'utf-8' }, function(err, data) {
      if (err) return reject(err);
      console.log('received data: ' + data);

      resolve(data);
    });
  }).then(function(data) {
    return db.runSql(data);
  });
};

exports.down = function(db) {
  var filePath = path.join(__dirname, 'sqls', '20170728133556-initial-down.sql');
  return new Promise(function(resolve, reject) {
    fs.readFile(filePath, { encoding: 'utf-8' }, function(err, data) {
      if (err) return reject(err);
      console.log('received data: ' + data);

      resolve(data);
    });
  }).then(function(data) {
    return db.runSql(data);
  });
};

exports._meta = {
  version: 1,
};
<|endoftext|>


<commit_before>var signalExit = require('signal-exit')
var spawn = require('child_process').spawn

module.exports = function (program, args) {
  if (Array.isArray(program)) {
    args = program.slice(1)
    program = program[0]
  } else if (!Array.isArray(args)) {
    args = [].slice.call(arguments, 1)
  }

  var child = spawn(program, args, { stdio: 'inherit' })

  var childExited = false
  signalExit(function (code, signal) {
    child.kill(signal || 'SIGHUP')
  })

  child.on('close', function (code, signal) {
    childExited = true
    if (signal) {
      // If there is nothing else keeping the event loop alive,
      // then there's a race between a graceful exit and getting
      // the signal to this process.  Put this timeout here to
      // make sure we're still alive to get the signal, and thus
      // exit with the intended signal code.
      setTimeout(function () {}, 200)
      process.kill(process.pid, signal)
    } else
      process.exit(code)
  })

  return child
}
<commit_msg>beforeExit hook<commit_after>var signalExit = require('signal-exit')
var spawn = require('child_process').spawn

module.exports = function (program, args, cb) {

  if (typeof args === 'function') {
    cb = args
    args = undefined
  }

  cb = cb || function (done) {
    return done()
  }

  if (Array.isArray(program)) {
    args = program.slice(1)
    program = program[0]
  } else if (!Array.isArray(args)) {
    args = [].slice.call(arguments, 1)
  }

  var child = spawn(program, args, { stdio: 'inherit' })

  var childExited = false
  signalExit(function (code, signal) {
    child.kill(signal || 'SIGHUP')
  })

  child.on('close', function (code, signal) {
    cb(function () {
      childExited = true
      if (signal) {
        // If there is nothing else keeping the event loop alive,
        // then there's a race between a graceful exit and getting
        // the signal to this process.  Put this timeout here to
        // make sure we're still alive to get the signal, and thus
        // exit with the intended signal code.
        setTimeout(function () {}, 200)
        process.kill(process.pid, signal)
      } else
        process.exit(code)
    })
  })

  return child
}
<|endoftext|>


<commit_before>/*---------------------------------------------------------
 * OpenERP web_graph
 *---------------------------------------------------------*/

openerp.web_graph = function (instance) {

var _lt = instance.web._lt;

// removed ``undefined`` values
var filter_values = function (o) {
    var out = {};
    for (var k in o) {
        if (!o.hasOwnProperty(k) || o[k] === undefined) { continue; }
        out[k] = o[k];
    }
    return out;
};

instance.web.views.add('graph', 'instance.web_graph.GraphView');
instance.web_graph.GraphView = instance.web.View.extend({
    template: "GraphView",
    display_name: _lt('Graph'),
    view_type: "graph",

    init: function(parent, dataset, view_id, options) {
        var self = this;
        this._super(parent);
        this.set_default_options(options);
        this.dataset = dataset;
        this.view_id = view_id;

        this.mode = "bar";          // line, bar, area, pie, radar
        this.orientation = false;    // true: horizontal, false: vertical
        this.stacked = true;

        this.spreadsheet = false;   // Display data grid, allows copy to CSV
        this.forcehtml = false;
        this.legend = "top";        // top, inside, no

        this.domain = [];
        this.context = {};
        this.group_by = [];

        this.graph = null;
    },
    view_loading: function(r) {
        return this.load_graph(r);
    },
    destroy: function () {
        if (this.graph) {
            this.graph.destroy();
        }
        this._super();
    },

    load_graph: function(fields_view_get) {
        // TODO: move  to load_view and document
        var self = this;
        this.fields_view = fields_view_get;
        this.$el.addClass(this.fields_view.arch.attrs['class']);

        this.mode = this.fields_view.arch.attrs.type || 'bar';
        this.orientation = this.fields_view.arch.attrs.orientation == 'horizontal';

        var width = this.$el.parent().width();
        this.$el.css("width", width);
        this.container = this.$el.find("#editor-render-body").css({
            width: width,
            height: Math.min(500, width * 0.8)
        })[0];

        var graph_render = this.proxy('graph_render');
        this.$el.on('click', '.oe_graph_options a', function (evt) {
            var $el = $(evt.target);

            self.graph_render({data: filter_values({
                mode: $el.data('mode'),
                legend: $el.data('legend'),
                orientation: $el.data('orientation'),
                stacked: $el.data('stacked')
            })});
        });

        this.$el.find("#graph_show_data").click(function () {
            self.spreadsheet = ! self.spreadsheet;
            self.graph_render();
        });
        this.$el.find("#graph_switch").click(function () {
            if (self.mode != 'radar') {
                self.orientation = ! self.orientation;
            }
            self.graph_render();
        });

        this.$el.find("#graph_download").click(function () {
            if (self.legend == "top") { self.legend = "inside"; }
            self.forcehtml = true;

            self.graph_get_data().done(function (result) {
                self.graph_render_all(result).download.saveImage('png');
            }).always(function () {
                self.forcehtml = false;
            });
        });
        this.trigger('graph_view_loaded', fields_view_get);
    },

    get_format: function (options) {
        options = options || {};
        var legend = {
            show: this.legend != 'no',
        };

        switch (this.legend) {
        case 'top':
            legend.noColumns = 4;
            legend.container = this.$el.find("div.graph_header_legend")[0];
            break;
        case 'inside':
            legend.position = 'nw';
            legend.backgroundColor = '#D2E8FF';
            break;
        }

        return _.extend({
            legend: legend,
            mouse: {
                track: true,
                relative: true
            },
            spreadsheet : {
                show: this.spreadsheet,
                initialTab: "data"
            },
            HtmlText : (options.xaxis && options.xaxis.labelsAngle) ? false : !this.forcehtml,
        }, options);
    },

    make_graph: function (mode, container, data) {
        if (mode === 'area') { mode = 'line'; }
        var format = this.get_format(this['options_' + mode](data));
        return Flotr.draw(container, data.data, format);
    },

    options_bar: function (data) {
        var min = _(data.data).chain()
            .map(function (record) {
                if (record.data.length > 0){
	                return _.min(record.data, function (item) {
	                    return item[1];
	                })[1];
                }
            }).min().value();
        return {
            bars : {
                show : true,
                stacked : this.stacked,
                horizontal : this.orientation,
                barWidth : 0.7,
                lineWidth : 1
            },
            grid : {
                verticalLines : this.orientation,
                horizontalLines : !this.orientation,
                outline : "sw",
            },
            yaxis : {
                ticks: this.orientation ? data.ticks : false,
                min: !this.orientation ? (min < 0 ? min : 0) : null
            },
            xaxis : {
                labelsAngle: 45,
                ticks: this.orientation ? false : data.ticks,
                min: this.orientation ? (min < 0 ? min : 0) : null
            }
        };
    },

    options_pie: function (data) {
        return {
            pie : {
                show: true
            },
            grid : {
                verticalLines : false,
                horizontalLines : false,
                outline : "",
            },
            xaxis :  {showLabels: false},
            yaxis :  {showLabels: false},
        };
    },

    options_radar: function (data) {
        return {
            radar : {
                show : true,
                stacked : this.stacked
            },
            grid : {
                circular : true,
                minorHorizontalLines : true
            },
            xaxis : {
                ticks: data.ticks
            },
        };
    },

    options_line: function (data) {
        return {
            lines : {
                show : true,
            },
            points: {
                show: true,
            },
            grid : {
                verticalLines : this.orientation,
                horizontalLines : !this.orientation,
                outline : "sw",
            },
            yaxis : {
                ticks: this.orientation ? data.ticks : false
            },
            xaxis : {
                labelsAngle: 45,
                ticks: this.orientation ? false : data.ticks
            }
        };
    },

    graph_get_data: function () {
        var model = this.dataset.model,
            domain = new instance.web.CompoundDomain(this.domain || []),
            context = new instance.web.CompoundContext(this.context || {}),
            group_by = this.group_by || [],
            view_id = this.view_id  || false,
            mode = this.mode || 'bar',
            orientation = this.orientation || false,
            stacked = this.stacked || false;

        var obj = new instance.web.Model(model);
        var view_get;
        var fields;
        var result = [];
        var ticks = {};

        return this.alive(obj.call("fields_view_get", [view_id, 'graph', context]).then(function(tmp) {
            view_get = tmp;
            fields = view_get['fields'];
            var toload = _.select(group_by, function(x) { return fields[x] === undefined });
            if (toload.length >= 1)
                return obj.call("fields_get", [toload, context]);
            else
                return $.when([]);
        }).then(function (fields_to_add) {
            _.extend(fields, fields_to_add);

            var tree = $($.parseXML(view_get['arch']));

            var pos = 0;
            var xaxis = _.clone(group_by || []);
            var yaxis = [];
            tree.find("field").each(function() {
                var field = $(this);
                if (! field.attr("name"))
                    return;
                if ((group_by.length == 0) && ((! pos) || instance.web.py_eval(field.attr('group') || "false"))) {
                    xaxis.push(field.attr('name'));
                }
                if (pos && ! instance.web.py_eval(field.attr('group') || "false")) {
                    yaxis.push(field.attr('name'));
                }
                pos += 1;
            });

            if (xaxis.length === 0)
                throw new Error("No field for the X axis!");
            if (yaxis.length === 0)
                throw new Error("No field for the Y axis!");

            // Convert a field's data into a displayable string

            function _convert_key(field, data) {
                if (fields[field]['type'] === 'many2one')
                    data = data && data[0];
                return data;
            }

            function _convert(field, data, tick) {
                tick = tick === undefined ? true : false;
                try {
                    data = instance.web.format_value(data, fields[field]);
                } catch(e) {
                    data = "" + data;
                }
                if (tick) {
                    if (ticks[data] === undefined)
                        ticks[data] = _.size(ticks);
                    return ticks[data];
                }
                return data || 0;
            }

            function _orientation(x, y) {
                if (! orientation)
                    return [x, y]
                return [y, x]
            }

            if (mode === "pie") {
                return obj.call("read_group", [domain, yaxis.concat([xaxis[0]]), [xaxis[0]]], {context: context}).then(function(res) {
                    _.each(res, function(record) {
                        result.push({
                            'data': [[_convert(xaxis[0], record[xaxis[0]]), record[yaxis[0]]]],
                            'label': _convert(xaxis[0], record[xaxis[0]], false)
                        });
                    });
                });
            } else if ((! stacked) || (xaxis.length < 2)) {
                var defs = [];
                _.each(xaxis, function(x) {
                    defs.push(obj.call("read_group", [domain, yaxis.concat([x]), [x]], {context: context}).then(function(res) {
                        return [x, res];
                    }));
                });
                return $.when.apply($, defs).then(function() {
                    _.each(_.toArray(arguments), function(res) {
                        var x = res[0];
                        res = res[1];
                        result.push({
                            'data': _.map(res, function(record) {
                                return _orientation(_convert(x, record[x]), record[yaxis[0]] || 0);
                            }),
                            'label': fields[x]['string']
                        });
                    });
                });
            } else {
                xaxis.reverse();
                return obj.call("read_group", [domain, yaxis.concat(xaxis.slice(0, 1)), xaxis.slice(0, 1)], {context: context}).then(function(axis) {
                    var defs = [];
                    _.each(axis, function(x) {
                        var key = x[xaxis[0]]
                        defs.push(obj.call("read_group", [x['__domain'], yaxis.concat(xaxis.slice(1, 2)), xaxis.slice(1, 2)], {context: context}).then(function(res) {
                            return [x, key, res];
                        }));
                    });
                    return $.when.apply($, defs).then(function() {
                        _.each(_.toArray(arguments), function(res) {
                            var x = res[0];
                            var key = res[1];
                            res = res[2];
                            result.push({
                                'data': _.map(res, function(record) {
                                    return _orientation(_convert(xaxis[1], record[xaxis[1]]), record[yaxis[0]] || 0);
                                }),
                                'label': _convert(xaxis[0], key, false)
                            })
                        });
                    });
                });
            }
        }).then(function() {
            var res = {
                'data': result,
                'ticks': _.map(ticks, function(el, key) { return [el, key] })
            };
            return res;
        }));
    },

    // Render the graph and update menu styles
    graph_render: function (options) {
        options = options || {};
        _.extend(this, options.data);

        return this.graph_get_data()
            .done(this.proxy('graph_render_all'));
    },

    graph_render_all: function (data) {
        var i;
        if (this.mode=='area') {
            for (i=0; i<data.data.length; i++) {
                data.data[i].lines = {fill: true}
            }
        }
        if (this.graph) {
            this.graph.destroy();
        }

        // Render the graph
        this.$el.find(".graph_header_legend").children().remove();
        this.graph = this.make_graph(this.mode, this.container, data);

        // Update styles of menus

        this.$el.find("a").removeClass("active");

        var $active = this.$el.find('a[data-mode=' + this.mode + ']');
        if ($active.length > 1) {
            $active = $active.filter('[data-stacked=' + this.stacked + ']');
        }
        $active = $active.add(
            this.$el.find('a:not([data-mode])[data-legend=' + this.legend + ']'));

        $active.addClass('active');

        if (this.spreadsheet) {
            this.$el.find("#graph_show_data").addClass("active");
        }
        return this.graph;
    },

    // render the graph using the domain, context and group_by
    // calls the 'graph_data_get' python controller to process all data
    // TODO: check is group_by should better be in the context
    do_search: function(domain, context, group_by) {
        this.domain = domain;
        this.context = context;
        this.group_by = group_by;

        this.graph_render();
    },

    do_show: function() {
        this.do_push_state({});
        return this._super();
    },
});
};
<commit_msg>[FIX] display 'undefined' in case of falsy graph value rather than empty label<commit_after>/*---------------------------------------------------------
 * OpenERP web_graph
 *---------------------------------------------------------*/

openerp.web_graph = function (instance) {

var _lt = instance.web._lt;
var _t = instance.web._t;

// removed ``undefined`` values
var filter_values = function (o) {
    var out = {};
    for (var k in o) {
        if (!o.hasOwnProperty(k) || o[k] === undefined) { continue; }
        out[k] = o[k];
    }
    return out;
};

instance.web.views.add('graph', 'instance.web_graph.GraphView');
instance.web_graph.GraphView = instance.web.View.extend({
    template: "GraphView",
    display_name: _lt('Graph'),
    view_type: "graph",

    init: function(parent, dataset, view_id, options) {
        var self = this;
        this._super(parent);
        this.set_default_options(options);
        this.dataset = dataset;
        this.view_id = view_id;

        this.mode = "bar";          // line, bar, area, pie, radar
        this.orientation = false;    // true: horizontal, false: vertical
        this.stacked = true;

        this.spreadsheet = false;   // Display data grid, allows copy to CSV
        this.forcehtml = false;
        this.legend = "top";        // top, inside, no

        this.domain = [];
        this.context = {};
        this.group_by = [];

        this.graph = null;
    },
    view_loading: function(r) {
        return this.load_graph(r);
    },
    destroy: function () {
        if (this.graph) {
            this.graph.destroy();
        }
        this._super();
    },

    load_graph: function(fields_view_get) {
        // TODO: move  to load_view and document
        var self = this;
        this.fields_view = fields_view_get;
        this.$el.addClass(this.fields_view.arch.attrs['class']);

        this.mode = this.fields_view.arch.attrs.type || 'bar';
        this.orientation = this.fields_view.arch.attrs.orientation == 'horizontal';

        var width = this.$el.parent().width();
        this.$el.css("width", width);
        this.container = this.$el.find("#editor-render-body").css({
            width: width,
            height: Math.min(500, width * 0.8)
        })[0];

        var graph_render = this.proxy('graph_render');
        this.$el.on('click', '.oe_graph_options a', function (evt) {
            var $el = $(evt.target);

            self.graph_render({data: filter_values({
                mode: $el.data('mode'),
                legend: $el.data('legend'),
                orientation: $el.data('orientation'),
                stacked: $el.data('stacked')
            })});
        });

        this.$el.find("#graph_show_data").click(function () {
            self.spreadsheet = ! self.spreadsheet;
            self.graph_render();
        });
        this.$el.find("#graph_switch").click(function () {
            if (self.mode != 'radar') {
                self.orientation = ! self.orientation;
            }
            self.graph_render();
        });

        this.$el.find("#graph_download").click(function () {
            if (self.legend == "top") { self.legend = "inside"; }
            self.forcehtml = true;

            self.graph_get_data().done(function (result) {
                self.graph_render_all(result).download.saveImage('png');
            }).always(function () {
                self.forcehtml = false;
            });
        });
        this.trigger('graph_view_loaded', fields_view_get);
    },

    get_format: function (options) {
        options = options || {};
        var legend = {
            show: this.legend != 'no',
        };

        switch (this.legend) {
        case 'top':
            legend.noColumns = 4;
            legend.container = this.$el.find("div.graph_header_legend")[0];
            break;
        case 'inside':
            legend.position = 'nw';
            legend.backgroundColor = '#D2E8FF';
            break;
        }

        return _.extend({
            legend: legend,
            mouse: {
                track: true,
                relative: true
            },
            spreadsheet : {
                show: this.spreadsheet,
                initialTab: "data"
            },
            HtmlText : (options.xaxis && options.xaxis.labelsAngle) ? false : !this.forcehtml,
        }, options);
    },

    make_graph: function (mode, container, data) {
        if (mode === 'area') { mode = 'line'; }
        var format = this.get_format(this['options_' + mode](data));
        return Flotr.draw(container, data.data, format);
    },

    options_bar: function (data) {
        var min = _(data.data).chain()
            .map(function (record) {
                if (record.data.length > 0){
	                return _.min(record.data, function (item) {
	                    return item[1];
	                })[1];
                }
            }).min().value();
        return {
            bars : {
                show : true,
                stacked : this.stacked,
                horizontal : this.orientation,
                barWidth : 0.7,
                lineWidth : 1
            },
            grid : {
                verticalLines : this.orientation,
                horizontalLines : !this.orientation,
                outline : "sw",
            },
            yaxis : {
                ticks: this.orientation ? data.ticks : false,
                min: !this.orientation ? (min < 0 ? min : 0) : null
            },
            xaxis : {
                labelsAngle: 45,
                ticks: this.orientation ? false : data.ticks,
                min: this.orientation ? (min < 0 ? min : 0) : null
            }
        };
    },

    options_pie: function (data) {
        return {
            pie : {
                show: true
            },
            grid : {
                verticalLines : false,
                horizontalLines : false,
                outline : "",
            },
            xaxis :  {showLabels: false},
            yaxis :  {showLabels: false},
        };
    },

    options_radar: function (data) {
        return {
            radar : {
                show : true,
                stacked : this.stacked
            },
            grid : {
                circular : true,
                minorHorizontalLines : true
            },
            xaxis : {
                ticks: data.ticks
            },
        };
    },

    options_line: function (data) {
        return {
            lines : {
                show : true,
            },
            points: {
                show: true,
            },
            grid : {
                verticalLines : this.orientation,
                horizontalLines : !this.orientation,
                outline : "sw",
            },
            yaxis : {
                ticks: this.orientation ? data.ticks : false
            },
            xaxis : {
                labelsAngle: 45,
                ticks: this.orientation ? false : data.ticks
            }
        };
    },

    graph_get_data: function () {
        var model = this.dataset.model,
            domain = new instance.web.CompoundDomain(this.domain || []),
            context = new instance.web.CompoundContext(this.context || {}),
            group_by = this.group_by || [],
            view_id = this.view_id  || false,
            mode = this.mode || 'bar',
            orientation = this.orientation || false,
            stacked = this.stacked || false;

        var obj = new instance.web.Model(model);
        var view_get;
        var fields;
        var result = [];
        var ticks = {};

        return this.alive(obj.call("fields_view_get", [view_id, 'graph', context]).then(function(tmp) {
            view_get = tmp;
            fields = view_get['fields'];
            var toload = _.select(group_by, function(x) { return fields[x] === undefined });
            if (toload.length >= 1)
                return obj.call("fields_get", [toload, context]);
            else
                return $.when([]);
        }).then(function (fields_to_add) {
            _.extend(fields, fields_to_add);

            var tree = $($.parseXML(view_get['arch']));

            var pos = 0;
            var xaxis = _.clone(group_by || []);
            var yaxis = [];
            tree.find("field").each(function() {
                var field = $(this);
                if (! field.attr("name"))
                    return;
                if ((group_by.length == 0) && ((! pos) || instance.web.py_eval(field.attr('group') || "false"))) {
                    xaxis.push(field.attr('name'));
                }
                if (pos && ! instance.web.py_eval(field.attr('group') || "false")) {
                    yaxis.push(field.attr('name'));
                }
                pos += 1;
            });

            if (xaxis.length === 0)
                throw new Error("No field for the X axis!");
            if (yaxis.length === 0)
                throw new Error("No field for the Y axis!");

            // Convert a field's data into a displayable string

            function _convert_key(field, data) {
                if (fields[field]['type'] === 'many2one')
                    data = data && data[0];
                return data;
            }

            function _convert(field, data, tick) {
                tick = tick === undefined ? true : false;
                try {
                    data = instance.web.format_value(data, fields[field]);
                } catch(e) {
                    data = "" + data;
                }
                if (tick) {
                    if (ticks[data] === undefined)
                        ticks[data] = _.size(ticks);
                    return ticks[data];
                }
                return data || 0;
            }

            function _orientation(x, y) {
                if (! orientation)
                    return [x, y]
                return [y, x]
            }

            if (mode === "pie") {
                return obj.call("read_group", [domain, yaxis.concat([xaxis[0]]), [xaxis[0]]], {context: context}).then(function(res) {
                    _.each(res, function(record) {
                        result.push({
                            'data': [[_convert(xaxis[0], record[xaxis[0]]), record[yaxis[0]]]],
                            'label': _convert(xaxis[0], record[xaxis[0]], false)
                        });
                    });
                });
            } else if ((! stacked) || (xaxis.length < 2)) {
                var defs = [];
                _.each(xaxis, function(x) {
                    defs.push(obj.call("read_group", [domain, yaxis.concat([x]), [x]], {context: context}).then(function(res) {
                        return [x, res];
                    }));
                });
                return $.when.apply($, defs).then(function() {
                    _.each(_.toArray(arguments), function(res) {
                        var x = res[0];
                        res = res[1];
                        result.push({
                            'data': _.map(res, function(record) {
                                return _orientation(_convert(x, record[x]), record[yaxis[0]] || 0);
                            }),
                            'label': fields[x]['string']
                        });
                    });
                });
            } else {
                xaxis.reverse();
                return obj.call("read_group", [domain, yaxis.concat(xaxis.slice(0, 1)), xaxis.slice(0, 1)], {context: context}).then(function(axis) {
                    var defs = [];
                    _.each(axis, function(x) {
                        var key = x[xaxis[0]]
                        defs.push(obj.call("read_group", [x['__domain'], yaxis.concat(xaxis.slice(1, 2)), xaxis.slice(1, 2)], {context: context}).then(function(res) {
                            return [x, key, res];
                        }));
                    });
                    return $.when.apply($, defs).then(function() {
                        _.each(_.toArray(arguments), function(res) {
                            var x = res[0];
                            var key = res[1];
                            res = res[2];
                            result.push({
                                'data': _.map(res, function(record) {
                                    return _orientation(_convert(xaxis[1], record[xaxis[1]]), record[yaxis[0]] || 0);
                                }),
                                'label': _convert(xaxis[0], key, false)
                            })
                        });
                    });
                });
            }
        }).then(function() {
            var res = {
                'data': result,
                'ticks': _.map(ticks, function(el, key) { return [el, key || _t("Undefined")] })
            };
            return res;
        }));
    },

    // Render the graph and update menu styles
    graph_render: function (options) {
        options = options || {};
        _.extend(this, options.data);

        return this.graph_get_data()
            .done(this.proxy('graph_render_all'));
    },

    graph_render_all: function (data) {
        var i;
        if (this.mode=='area') {
            for (i=0; i<data.data.length; i++) {
                data.data[i].lines = {fill: true}
            }
        }
        if (this.graph) {
            this.graph.destroy();
        }

        // Render the graph
        this.$el.find(".graph_header_legend").children().remove();
        this.graph = this.make_graph(this.mode, this.container, data);

        // Update styles of menus

        this.$el.find("a").removeClass("active");

        var $active = this.$el.find('a[data-mode=' + this.mode + ']');
        if ($active.length > 1) {
            $active = $active.filter('[data-stacked=' + this.stacked + ']');
        }
        $active = $active.add(
            this.$el.find('a:not([data-mode])[data-legend=' + this.legend + ']'));

        $active.addClass('active');

        if (this.spreadsheet) {
            this.$el.find("#graph_show_data").addClass("active");
        }
        return this.graph;
    },

    // render the graph using the domain, context and group_by
    // calls the 'graph_data_get' python controller to process all data
    // TODO: check is group_by should better be in the context
    do_search: function(domain, context, group_by) {
        this.domain = domain;
        this.context = context;
        this.group_by = group_by;

        this.graph_render();
    },

    do_show: function() {
        this.do_push_state({});
        return this._super();
    },
});
};
<|endoftext|>


<commit_before>var expect = require('expect.js'),
    defaultOpts = require('cheerio').prototype.options,
    _ = require('lodash'),
    parse = require('cheerio/lib/parse'),
    render = require('./index.js');

var html = function(str, options) {
  options = _.defaults(options || {}, defaultOpts);
  var dom = parse(str, options);
  return render(dom);
};

var xml = function(str, options) {
  options = _.defaults(options || {}, defaultOpts);
  options.xmlMode = true;
  var dom = parse(str, options);
  return render(dom, options);
};

describe('render', function() {

  describe('(html)', function() {

    it('should render
 tags correctly', function() {
      var str = '
';
      expect(html(str)).to.equal('
');
    });

    it('should handle double quotes within single quoted attributes properly', function() {
      var str = '
';
      expect(html(str)).to.equal('
');
    });

    it('should retain encoded HTML content within attributes', function() {
      var str = '
';
      expect(html(str)).to.equal('
');
    });

    it('should shorten the "checked" attribute when it contains the value "checked"', function() {
      var str = '<input checked/>';
      expect(html(str)).to.equal('<input checked>');
    });

    it('should not shorten the "name" attribute when it contains the value "name"', function() {
      var str = '<input name="name"/>';
      expect(html(str)).to.equal('<input name="name">');
    });

    it('should render comments correctly', function() {
      var str = '';
      expect(html(str)).to.equal('');
    });

    it('should render whitespace by default', function() {
      var str = 'hi blah';
      expect(html(str)).to.equal(str);
    });

    it('should normalize whitespace if specified', function() {
      var str = 'hi blah  ';
      expect(html(str, { normalizeWhitespace: true })).to.equal('hi blah ');
    });

    it('should preserve multiple hyphens in data attributes', function() {
      var str = '

';
      expect(html(str)).to.equal('

');
    });

    it('should render CDATA correctly', function() {
      var str = '  <![CDATA[ asdf&asdf ]]> <c/> <![CDATA[ asdf&asdf ]]>  ';
      expect(xml(str)).to.equal(str);
    });

  });

});
<commit_msg>test: add case for script tag<commit_after>var expect = require('expect.js'),
    defaultOpts = require('cheerio').prototype.options,
    _ = require('lodash'),
    parse = require('cheerio/lib/parse'),
    render = require('./index.js');

var html = function(str, options) {
  options = _.defaults(options || {}, defaultOpts);
  var dom = parse(str, options);
  return render(dom);
};

var xml = function(str, options) {
  options = _.defaults(options || {}, defaultOpts);
  options.xmlMode = true;
  var dom = parse(str, options);
  return render(dom, options);
};

describe('render', function() {

  describe('(html)', function() {

    it('should render
 tags correctly', function() {
      var str = '
';
      expect(html(str)).to.equal('
');
    });

    it('should handle double quotes within single quoted attributes properly', function() {
      var str = '
';
      expect(html(str)).to.equal('
');
    });

    it('should retain encoded HTML content within attributes', function() {
      var str = '
';
      expect(html(str)).to.equal('
');
    });

    it('should shorten the "checked" attribute when it contains the value "checked"', function() {
      var str = '<input checked/>';
      expect(html(str)).to.equal('<input checked>');
    });

    it('should not shorten the "name" attribute when it contains the value "name"', function() {
      var str = '<input name="name"/>';
      expect(html(str)).to.equal('<input name="name">');
    });

    it('should render comments correctly', function() {
      var str = '';
      expect(html(str)).to.equal('');
    });

    it('should render whitespace by default', function() {
      var str = 'hi blah';
      expect(html(str)).to.equal(str);
    });

    it('should normalize whitespace if specified', function() {
      var str = 'hi blah  ';
      expect(html(str, { normalizeWhitespace: true })).to.equal('hi blah ');
    });

    it('should preserve multiple hyphens in data attributes', function() {
      var str = '

';
      expect(html(str)).to.equal('

');
    });

    it('should not encode characters in script tag', function() {
      var str = '';
      expect(html(str)).to.equal(str);
    });

    it('should render CDATA correctly', function() {
      var str = '  <![CDATA[ asdf&asdf ]]> <c/> <![CDATA[ asdf&asdf ]]>  ';
      expect(xml(str)).to.equal(str);
    });

  });

});
<|endoftext|>


<commit_before>//import * as ChannelShuffler from "../Layer/ChannelShuffler.js";

/**
 * Test different resize implementation for CNN.
 *
 * @see {@link https://jsperf.com/colorfulcakechen-cnn-height-width-resize}
 */

/**
 * A test set.
 */
class HeightWidthDepth {

  /**
   * @param {number} height      image height
   * @param {number} width       image width
   * @param {number} depth       image channel count
   */
  constructor( height, width, depth ) {

    this.height = height;
    this.width = width;
    this.depth = depth;

    this.valueCount = height * width * depth;

    //this.concatenatedShape = [ height, width, depth ];
    this.targetSize = [ ( height / 2 ), ( width / 2 ) ];

    this.dataTensor3d = tf.tidy( () => {
      let dataTensor1d = tf.linspace( 0, this.valueCount - 1, this.valueCount );
      let dataTensor3d = dataTensor1d.reshape( [ height, width, depth ] );
      return dataTensor3d;
    });

    this.depthwiseConvFilters = tf.tidy( () => {
      let filterHeight =      targetSize[ 0 ] + 1;
      let filterWidth =       targetSize[ 1 ] + 1;
      let inChannels =        depth;
      let channelMultiplier = 1;
      let filtersShape = [ filterHeight, filterWidth, inChannels, channelMultiplier ];

      let filtersTensor1d = tf.range( 1, this.valueCount, 1 );
      let filtersTensor4d = tf.tensor4d( filtersTensor1d, filtersShape );
      return filtersTensor4d;
    });
  }

  disposeTensors() {
    if ( this.dataTensor3d ) {
      tf.dispose( this.dataTensor3d );
      this.dataTensor3d = null;
    }

    if ( this.depthwiseConvFilters ) {
      tf.dispose( this.depthwiseConvFilters );
      this.depthwiseConvFilters = null;
    }
  }

  // Test max-pool
  test_MaxPool() {
    tf.tidy( () => {
      let quarter = this.dataTensor3d.maxPool( 2, 1, "valid" );
    });
  }

  // Test avg-pool
  test_AvgPool() {
    tf.tidy( () => {
      let quarter = this.dataTensor3d.avgPool( 2, 1, "valid" );
    });
  }

  // Test depthwise convolution (2D)
  test_DepthwiseConv2d() {
    tf.tidy( () => {
      let quarter = this.dataTensor3d.depthwiseConv2d( this.depthwiseConvFilters, 1, "valid" );
    });
  }

  // Test rsize-nearest-neighbor
  test_ResizeNearestNeighbor() {
    tf.tidy( () => {
      let quarter = this.dataTensor3d.resizeNearestNeighbor( this.targetSize, true );
    });
  }

  // Test rsize-bilinear
  test_ResizeBilinear() {
    tf.tidy( () => {
      let quarter = this.dataTensor3d.resizeBilinear( this.targetSize, true );
    });
  }

  // Testing whether the results of different implementation are the same.
  testResultSame() {
    tf.tidy( () => {
      test_MaxPool();
      test_AvgPool();
      test_DepthwiseConv2d();
      test_ResizeNearestNeighbor();
      test_ResizeBilinear();

//      tf.util.assert(
//        ChannelShuffler.Layer.isTensorArrayEqual( t1Array, t2Array ),
//        `ConcatReshapeTransposeReshapeSplit() != ConcatGatherUnsorted()`);
    });
  }

}

globalThis.testSet_110x110x24 = new HeightWidthDepthGroup( 110, 110, 24 ); // height, width, depth
<commit_msg>Update jsPerf_CNNHeightWidthResize.js<commit_after>//import * as ChannelShuffler from "../Layer/ChannelShuffler.js";

/**
 * Test different resize implementation for CNN.
 *
 * @see {@link https://jsperf.com/colorfulcakechen-cnn-height-width-resize}
 */

/**
 * A test set.
 */
class HeightWidthDepth {

  /**
   * @param {number} height      image height
   * @param {number} width       image width
   * @param {number} depth       image channel count
   */
  constructor( height, width, depth ) {

    this.height = height;
    this.width = width;
    this.depth = depth;

    this.valueCount = height * width * depth;

    //this.concatenatedShape = [ height, width, depth ];
    this.targetSize = [ ( height / 2 ), ( width / 2 ) ];

    this.dataTensor3d = tf.tidy( () => {
      let dataTensor1d = tf.linspace( 0, this.valueCount - 1, this.valueCount );
      let dataTensor3d = dataTensor1d.reshape( [ height, width, depth ] );
      return dataTensor3d;
    });

    this.depthwiseConvFilters = tf.tidy( () => {
      let filterHeight =      targetSize[ 0 ] + 1;
      let filterWidth =       targetSize[ 1 ] + 1;
      let inChannels =        depth;
      let channelMultiplier = 1;
      let filtersShape = [ filterHeight, filterWidth, inChannels, channelMultiplier ];

      let filtersTensor1d = tf.range( 1, this.valueCount, 1 );
      let filtersTensor4d = tf.tensor4d( filtersTensor1d, filtersShape );
      return filtersTensor4d;
    });
  }

  disposeTensors() {
    if ( this.dataTensor3d ) {
      tf.dispose( this.dataTensor3d );
      this.dataTensor3d = null;
    }

    if ( this.depthwiseConvFilters ) {
      tf.dispose( this.depthwiseConvFilters );
      this.depthwiseConvFilters = null;
    }
  }

  // Test max-pool
  test_MaxPool() {
    tf.tidy( () => {
      let quarter = this.dataTensor3d.maxPool( 2, 1, "valid" );
    });
  }

  // Test avg-pool
  test_AvgPool() {
    tf.tidy( () => {
      let quarter = this.dataTensor3d.avgPool( 2, 1, "valid" );
    });
  }

  // Test depthwise convolution (2D)
  test_DepthwiseConv2d() {
    tf.tidy( () => {
      let quarter = this.dataTensor3d.depthwiseConv2d( this.depthwiseConvFilters, 1, "valid" );
    });
  }

  // Test rsize-nearest-neighbor
  test_ResizeNearestNeighbor() {
    tf.tidy( () => {
      let quarter = this.dataTensor3d.resizeNearestNeighbor( this.targetSize, true );
    });
  }

  // Test rsize-bilinear
  test_ResizeBilinear() {
    tf.tidy( () => {
      let quarter = this.dataTensor3d.resizeBilinear( this.targetSize, true );
    });
  }

  // Testing whether the results of different implementation are the same.
  testResultSame() {
    tf.tidy( () => {
      test_MaxPool();
      test_AvgPool();
      test_DepthwiseConv2d();
      test_ResizeNearestNeighbor();
      test_ResizeBilinear();

//      tf.util.assert(
//        ChannelShuffler.Layer.isTensorArrayEqual( t1Array, t2Array ),
//        `ConcatReshapeTransposeReshapeSplit() != ConcatGatherUnsorted()`);
    });
  }

}

globalThis.testSet_110x110x24 = new HeightWidthDepth( 110, 110, 24 ); // height, width, depth
<|endoftext|>