<commit_before>// Filter out tasks that don't need to be built. Determines which tasks need to be built based on the type of build.
// If its a CI build, all tasks whose package numbers have been bumped will be built.
// If its a PR build, all tasks that have been changed will be built.
// Any other type of build will build all tasks.
var fs = require('fs');
var path = require('path');
var semver = require('semver');
var restClient = require('typed-rest-client/RestClient');
var httpHandler = require('typed-rest-client/Handlers');
var run = require('./ci-util').run;

var makeOptionsPath = path.join(__dirname, '..', 'make-options.json');
var makeOptions = JSON.parse(fs.readFileSync(makeOptionsPath).toString());

var getTasksToBuildForCI = async function() {
    // Returns a list of tasks that have different version numbers than their current published version.
    var packageInfo;
    try {
        var packageToken = process.env['PACKAGE_TOKEN'];
        if (!packageToken) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=21;]Failed to get info from package endpoint because no token was provided. Try setting the PACKAGE_TOKEN environment variable.`);
            return makeOptions.tasks;
        }
        var handler = new httpHandler.PersonalAccessTokenCredentialHandler(packageToken);
        var client = new restClient.RestClient('azure-pipelines-tasks-ci', '', [handler]);
        var packageEndpoint = process.env['PACKAGE_ENDPOINT'];
        if (!packageEndpoint) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=28;]Failed to get info from package endpoint because no endpoint was specified. Try setting the PACKAGE_ENDPOINT environment variable.`);
            return makeOptions.tasks;
        }
        packageInfo = await client.get(packageEndpoint);
        if (packageInfo.statusCode !== 200) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=33;]Failed to get info from package endpoint, returned with status code ${packageInfo.statusCode}`);
            return makeOptions.tasks;
        }
    }
    catch (err) {
        // If unable to reach rest client, build everything.
        console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=39;]Failed to get info from package endpoint, client failed with error ${err.message}`);
        return makeOptions.tasks;
    }
    var packageMap = {};
    if(!packageInfo.result || !packageInfo.result.value) {
        console.log('##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=44;]Failed to get info from package endpoint, returned no packages');
        return makeOptions.tasks;
    }
    packageInfo.result.value.forEach(package => {
        if (package.name && package.versions) {
            var packageName = package.name.slice('Mseng.MS.TF.DistributedTask.Tasks.'.length).toLowerCase();
            packageMap[packageName] = package.versions[0].version;
            package.versions.some(versionInfo => {
                if (versionInfo.isLatest) {
                    packageMap[packageName] = versionInfo.version;
                    return true;
                }
                return false;
            });
        }
    });

    return makeOptions.tasks.filter(function (taskName) {
        var taskJsonPath = path.join(__dirname, '..', 'Tasks' , taskName, 'task.json');
        if (fs.existsSync(taskJsonPath)){
            var taskJson = JSON.parse(fs.readFileSync(taskJsonPath).toString());
            var lowerCaseName = taskJson.name.toLowerCase();
            if (lowerCaseName in packageMap) {
                var packageVersion = packageMap[lowerCaseName];
                var localVersion = `${taskJson.version.Major}.${taskJson.version.Minor}.${taskJson.version.Patch}`;

                // Build if local version and package version are different.
                return semver.neq(localVersion, packageVersion);
            }
            else {
                console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=74;]${taskName} has not been published before`);
                return true;
            }
        }
        else {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=79;]${taskJsonPath} does not exist`);
            return true;
        }
    });
}

var getTasksDependentOnChangedCommonFiles = function(commonFilePaths) {
    // Takes in an array of filepaths that have been changed in the Tasks/Common folder, returns any tasks that could be affected.
    if (commonFilePaths.length == 0) {
        return [];
    }
    changedTasks = [];
    makeOptions.tasks.forEach(taskName => {
        var makeJsonPath = path.join(__dirname, '..', 'Tasks' , taskName, 'make.json')
        if (fs.existsSync(makeJsonPath)){
            var makeJson = JSON.parse(fs.readFileSync(makeJsonPath).toString());
            if (makeJson.common) {
                makeJson.common.forEach(commonModule => {
                    if (commonFilePaths.includes(commonModule.module.toLowerCase()) && !changedTasks.includes(taskName)) {
                        changedTasks.push(taskName);
                    }
                });
            }
        }
    });
    return changedTasks;
}

var getTasksToBuildForPR = function() {
    // Takes in a git source branch, diffs it with master, and returns a list of tasks that could have been affected by the changes.
    var sourceBranch = process.env['SYSTEM_PULLREQUEST_SOURCEBRANCH'];
    var prId = process.env['SYSTEM_PULLREQUEST_PULLREQUESTNUMBER'];
    var commonChanges = [];
    var toBeBuilt = [];
    try {
        if (sourceBranch.includes(':')) {
            // We only care about the branch name, not the source repo
            sourceBranch = sourceBranch.split(':')[1];
        }
        run('git fetch origin pull/' + prId + '/head:' + sourceBranch);
    }
    catch (err) {
        // If unable to reach github, build everything.
        console.log('##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=124;]Unable to reach github, building all tasks', err);
        return makeOptions.tasks;
    }
    var baseCommit = run('git merge-base ' + sourceBranch + ' origin/master');
    run('git --no-pager diff --name-only ' + baseCommit + ' ' + sourceBranch)
        .split('\n')
        .forEach(filePath => {
        if (filePath.slice(0, 5) == 'Tasks') {
            var taskPath = filePath.slice(6);
            if(taskPath.slice(0, 6) == 'Common') {
                var commonName = taskPath.slice(7);
                commonChanges.push('../common/' + commonName.slice(0, commonName.indexOf('/')).toLowerCase());
            }
            else {
                var taskName = taskPath.slice(0, taskPath.indexOf('/'));
                if (!toBeBuilt.includes(taskName)) {
                    toBeBuilt.push(taskName);
                }
            }
        }
    });
    var changedTasks = getTasksDependentOnChangedCommonFiles(commonChanges);
    var shouldBeBumped = [];
    changedTasks.forEach(task => {
        if (!toBeBuilt.includes(task)) {
            shouldBeBumped.push(task);
        }
    });
    // TODO: Add this back once diffing is working 100%
//     if (shouldBeBumped.length > 0) {
//         throw new Error('The following tasks should have their versions bumped due to changes in common: ' + shouldBeBumped);
//     }
    return toBeBuilt;
}

var setTaskVariables = function(tasks) {
    console.log('tasks: ' + JSON.stringify(tasks));
    console.log('##vso[task.setVariable variable=task_pattern]@(' + tasks.join('|') + ')');
    console.log('##vso[task.setVariable variable=numTasks]' + tasks.length);
}

var buildReason = process.env['BUILD_REASON'].toLowerCase();
var tasks;
if (buildReason == 'individualci' || buildReason == 'batchedci') {
    // If CI, we will compare any tasks that have updated versions.
    getTasksToBuildForCI().then(tasks => {
        setTaskVariables(tasks)
    });
}
else {
    if (buildReason == 'pullrequest') {
        // If PR, we will compare any tasks that could have been affected based on the diff.
        tasks = getTasksToBuildForPR();
        setTaskVariables(tasks);
    }
    else {
        // If manual or other, build everything.
        setTaskVariables(makeOptions.tasks);
    }
}
<commit_msg>Revert "Fix task naming issue"<commit_after>// Filter out tasks that don't need to be built. Determines which tasks need to be built based on the type of build.
// If its a CI build, all tasks whose package numbers have been bumped will be built.
// If its a PR build, all tasks that have been changed will be built.
// Any other type of build will build all tasks.
var fs = require('fs');
var path = require('path');
var semver = require('semver');
var restClient = require('typed-rest-client/RestClient');
var httpHandler = require('typed-rest-client/Handlers');
var run = require('./ci-util').run;

var makeOptionsPath = path.join(__dirname, '..', 'make-options.json');
var makeOptions = JSON.parse(fs.readFileSync(makeOptionsPath).toString());

var getTasksToBuildForCI = async function() {
    // Returns a list of tasks that have different version numbers than their current published version.
    var packageInfo;
    try {
        var packageToken = process.env['PACKAGE_TOKEN'];
        if (!packageToken) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=21;]Failed to get info from package endpoint because no token was provided. Try setting the PACKAGE_TOKEN environment variable.`);
            return makeOptions.tasks;
        }
        var handler = new httpHandler.PersonalAccessTokenCredentialHandler(packageToken);
        var client = new restClient.RestClient('azure-pipelines-tasks-ci', '', [handler]);
        var packageEndpoint = process.env['PACKAGE_ENDPOINT'];
        if (!packageEndpoint) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=28;]Failed to get info from package endpoint because no endpoint was specified. Try setting the PACKAGE_ENDPOINT environment variable.`);
            return makeOptions.tasks;
        }
        packageInfo = await client.get(packageEndpoint);
        if (packageInfo.statusCode !== 200) {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=33;]Failed to get info from package endpoint, returned with status code ${packageInfo.statusCode}`);
            return makeOptions.tasks;
        }
    }
    catch (err) {
        // If unable to reach rest client, build everything.
        console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=39;]Failed to get info from package endpoint, client failed with error ${err.message}`);
        return makeOptions.tasks;
    }
    var packageMap = {};
    if(!packageInfo.result || !packageInfo.result.value) {
        console.log('##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=44;]Failed to get info from package endpoint, returned no packages');
        return makeOptions.tasks;
    }
    packageInfo.result.value.forEach(package => {
        if (package.name && package.versions) {
            var packageName = package.name.slice('Mseng.MS.TF.DistributedTask.Tasks.'.length).toLowerCase();
            packageMap[packageName] = package.versions[0].version;
            package.versions.some(versionInfo => {
                if (versionInfo.isLatest) {
                    packageMap[packageName] = versionInfo.version;
                    return true;
                }
                return false;
            });
        }
    });

    return makeOptions.tasks.filter(function (taskName) {
        var lowerCaseName = taskName.toLowerCase();
        if (lowerCaseName in packageMap) {
            var packageVersion = packageMap[lowerCaseName]

            var taskJsonPath = path.join(__dirname, '..', 'Tasks' , taskName, 'task.json');
            if (fs.existsSync(taskJsonPath)){
                var taskJson = JSON.parse(fs.readFileSync(taskJsonPath).toString());
                var localVersion = `${taskJson.version.Major}.${taskJson.version.Minor}.${taskJson.version.Patch}`;

                // Build if local version and package version are different.
                return semver.neq(localVersion, packageVersion);
            }
            else {
                console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=75;]${taskJsonPath} does not exist`);
                return true;
            }
        }
        else {
            console.log(`##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=80;]${taskName} has not been published before`);
            return true;
        }
    });
}

var getTasksDependentOnChangedCommonFiles = function(commonFilePaths) {
    // Takes in an array of filepaths that have been changed in the Tasks/Common folder, returns any tasks that could be affected.
    if (commonFilePaths.length == 0) {
        return [];
    }
    changedTasks = [];
    makeOptions.tasks.forEach(taskName => {
        var makeJsonPath = path.join(__dirname, '..', 'Tasks' , taskName, 'make.json')
        if (fs.existsSync(makeJsonPath)){
            var makeJson = JSON.parse(fs.readFileSync(makeJsonPath).toString());
            if (makeJson.common) {
                makeJson.common.forEach(commonModule => {
                    if (commonFilePaths.includes(commonModule.module.toLowerCase()) && !changedTasks.includes(taskName)) {
                        changedTasks.push(taskName);
                    }
                });
            }
        }
    });
    return changedTasks;
}

var getTasksToBuildForPR = function() {
    // Takes in a git source branch, diffs it with master, and returns a list of tasks that could have been affected by the changes.
    var sourceBranch = process.env['SYSTEM_PULLREQUEST_SOURCEBRANCH'];
    var prId = process.env['SYSTEM_PULLREQUEST_PULLREQUESTNUMBER'];
    var commonChanges = [];
    var toBeBuilt = [];
    try {
        if (sourceBranch.includes(':')) {
            // We only care about the branch name, not the source repo
            sourceBranch = sourceBranch.split(':')[1];
        }
        run('git fetch origin pull/' + prId + '/head:' + sourceBranch);
    }
    catch (err) {
        // If unable to reach github, build everything.
        console.log('##vso[task.logissue type=warning;sourcepath=ci/filter-task.js;linenumber=125;]Unable to reach github, building all tasks', err);
        return makeOptions.tasks;
    }
    var baseCommit = run('git merge-base ' + sourceBranch + ' origin/master');
    run('git --no-pager diff --name-only ' + baseCommit + ' ' + sourceBranch)
        .split('\n')
        .forEach(filePath => {
        if (filePath.slice(0, 5) == 'Tasks') {
            var taskPath = filePath.slice(6);
            if(taskPath.slice(0, 6) == 'Common') {
                var commonName = taskPath.slice(7);
                commonChanges.push('../common/' + commonName.slice(0, commonName.indexOf('/')).toLowerCase());
            }
            else {
                var taskName = taskPath.slice(0, taskPath.indexOf('/'));
                if (!toBeBuilt.includes(taskName)) {
                    toBeBuilt.push(taskName);
                }
            }
        }
    });
    var changedTasks = getTasksDependentOnChangedCommonFiles(commonChanges);
    var shouldBeBumped = [];
    changedTasks.forEach(task => {
        if (!toBeBuilt.includes(task)) {
            shouldBeBumped.push(task);
        }
    });
    // TODO: Add this back once diffing is working 100%
//     if (shouldBeBumped.length > 0) {
//         throw new Error('The following tasks should have their versions bumped due to changes in common: ' + shouldBeBumped);
//     }
    return toBeBuilt;
}

var setTaskVariables = function(tasks) {
    console.log('tasks: ' + JSON.stringify(tasks));
    console.log('##vso[task.setVariable variable=task_pattern]@(' + tasks.join('|') + ')');
    console.log('##vso[task.setVariable variable=numTasks]' + tasks.length);
}

var buildReason = process.env['BUILD_REASON'].toLowerCase();
var tasks;
if (buildReason == 'individualci' || buildReason == 'batchedci') {
    // If CI, we will compare any tasks that have updated versions.
    getTasksToBuildForCI().then(tasks => {
        setTaskVariables(tasks)
    });
}
else {
    if (buildReason == 'pullrequest') {
        // If PR, we will compare any tasks that could have been affected based on the diff.
        tasks = getTasksToBuildForPR();
        setTaskVariables(tasks);
    }
    else {
        // If manual or other, build everything.
        setTaskVariables(makeOptions.tasks);
    }
}
<|endoftext|>


<commit_before>export { ActivationFunction };

import { Int } from "./ValueDesc_Base.js";
import * as Weights from "../Weights.js";
import * as FloatValue from "../FloatValue.js";

/**
 * Describe activation function parameter's id, range, name.
 *
 * The activation function ActivationFunction.Info.pfn( x ) is almost linear when x is inside inputDomainLinear. Its output is
 * outputRangeLinear.
 *
 *   - CLIP_BY_VALUE_N2_P2( [ -2, +2 ] ) = [ -2, +2 ]
 *   - CLIP_BY_VALUE_N3_P3( [ -3, +3 ] ) = [ -3, +3 ]
 *   - RELU6( [ 0, 6 ] ) = [ 0, 6 ]
 *   - TANH ( [ -0.005, +0.005 ] ) = [ -0.005, +0.005]
 *   - SIN  ( [ -0.005, +0.005 ] ) = [ -0.005, +0.005]
 *   - COS  ( [ -( ( PI / 2 ) + 0.005 ), -( ( PI / 2 ) - 0.005 ) ] ) =  [ -0.005, +0.005 ]
 *   - SIGMOID ( [ -0.125, +0.125 ] ) = [ +0.468, +0.532 ]
 *   - RELU ( 0, +Infinity ) = [ 0, +Infinity ]
 *   - SOFTPLUS ( +5, +Infinity ) = [ +5, +Infinity ]
 *
 *
 * Note:
 *   - NONE: Beware. It easily results in infinity value because it does not have upper bound.
 *   - RELU: Beware. It easily results in infinity value because it does not have upper bound.
 *   - SOFTPLUS: Avoid. Backend WASM does not support it.
 *   - ERF: Avoid. Backend WASM does not support it.
 *

//!!! ...unfinished... (2022/07/14)
// Could [ -2, +2 ] be better than [ -3, +3 ]? Does two's power reduce floating-point accumulated error (after ShuffleNetV2_byMobileNetV2
// pass-through by scaling and un-scaling repeately)?

//!!! ...unfinished... (2022/01/11)
// What about: (tf.clipByValue() is also supported by WASM)
//   - tf.clipByValue( -1, +1 )?
//   - tf.clipByValue( -2, +2 )?
//   - tf.clipByValue( -3, +3 )?
//   - tf.clipByValue( -64, +64 )?
//   - tf.clipByValue( -128, +128 )?

 *   - CLIP_BY_VALUE, TANH, SIN, ERF are good if pass-through by only scale (i.e. without translate) is needed. Because the output
 *       range of them includes both negative value and positive value near the origin point. (On the other hand, RELU, RELU6 are
 *       not good for this purpose.)
 *
 *
 *
 * Q1: Since activation by clipByValue() is enough, why not just using saturated integer for convolution?
 * A1: Although saturated integer could achieve clipByValue automatically without activation function (i.e. less computation time).
 *     It has some disadvantages:
 *
 *      - It can not escape saturation (i.e. escape activation) by scaling. The reason is that needs multiply a number less
 *         than one which can not be represented by integer (i.e. floatig-point number is required).
 *
 *      - It may not be saturated (i.e. activated) by bias. When the input value is the minimum value (e.g. -255), adding a
 *         bias (even if adding the maximum value 255) can not saturate it to maximum value (e.g. +255).
 *
 * On the other hand, floating-point number has already been similar to a kind of saturated number (i.e. maximum is saturated to
 * +Infinity, and minimum is saturated to -Infinity).
 *
 *
 *
 * Q2: Why use CLIP_BY_VALUE_N2_P2 instead of CLIP_BY_VALUE_N3_P3?
 * A2: To reduce floating-point accumulated error.
 *
 * CLIP_BY_VALUE_N3_P3:
 *
 *   - Advantage: It has larger inputDomainLinear so that activation-escaping could use a larger scale. This reduces floating-point
 *       truncation error.
 *
 *   - Disadvantage: Its activated value (i.e. 3) seems harder to become any other value by a floating-point finite scaling.
 *       This increases the floating-point accumulated error. For example, in order to let 3 become 100, a scale 33.333...
 *       should be used. However, 33.3 can not be represented by a finite floating-point number.
 *
 * CLIP_BY_VALUE_N2_P2:
 *
 *   - Disadvantage: It has smaller inputDomainLinear [ -2, +2 ] (than CLIP_BY_VALUE_N3_P3 [ -3, +3 ]) so that activation-escaping
 *       needs use a smaller scale. This increases floating-point truncation error. Fortunately, if a just enough input value
 *       bounds is used (e.g. for RGBA image, uses 2^8 (instead of 2^24) as every channel's input value bounds), this issue could
 *       be less problematic.
 *
 *   - Advantage: Its activated value (i.e. 2) is easier to become any other value by a floating-point finite scaling.
 *       This reduces the floating-point accumulated error. In this point of view, not only 2, any powers of two (i.e. 0.25, 0.5, 1,
 *       2, 4, 8, ...) is good enough for this task.
 *
 */
class ActivationFunction extends Int {

  constructor() {

//!!! (2022/07/05 Remarked) For speed-up testing.
//    super( 0, 8,
//      [ "NONE",  "CLIP_BY_VALUE_N2_P2", "CLIP_BY_VALUE_N3_P3", "TANH",  "SIN", "RELU6",  "COS",  "SIGMOID", "RELU" ], //  "SOFTPLUS" ],

    super( 0, 1,
      { NONE : new ActivationFunction.Info( 0, "NONE", null, null, null, null, null ),

        CLIP_BY_VALUE_N2_P2: new ActivationFunction.Info( 1, "CLIP_BY_VALUE_N2_P2",
          ActivationFunction.clipByValue_Negative2_Positive2,
          ActivationFunction.reference_clipByValue_Negative2_Positive2,

//!!! (2022/08/03 Temp Remarked) Try large range whether reduceing accumulated error.
//          new FloatValue.Bounds( -2, +2 ), new FloatValue.Bounds( -2, +2 ), new FloatValue.Bounds( -2, +2 ) ),
//!!! (2022/08/03 Temp Remarked) Try small range whether reduceing accumulated error.
          new FloatValue.Bounds( -16, +16 ), new FloatValue.Bounds( -16, +16 ), new FloatValue.Bounds( -16, +16 ) ),
//          new FloatValue.Bounds( -1, +1 ), new FloatValue.Bounds( -1, +1 ), new FloatValue.Bounds( -1, +1 ) ),

//!!! (2022/07/05 Remarked) For speed-up testing.
/*
        CLIP_BY_VALUE_N3_P3: new ActivationFunction.Info( 2, "CLIP_BY_VALUE_N3_P3",
          ActivationFunction.clipByValue_Negative3_Positive3,
          ActivationFunction.reference_clipByValue_Negative3_Positive3,
          new FloatValue.Bounds( -3, +3 ), new FloatValue.Bounds( -3, +3 ), new FloatValue.Bounds( -3, +3 ) ),

        TANH: new ActivationFunction.Info( 3, "TANH",
          tf.tanh, ActivationFunction.reference_tanh,
          new FloatValue.Bounds( -1, +1 ), new FloatValue.Bounds( -0.005, +0.005 ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        SIN: new ActivationFunction.Info( 4, "SIN",
          tf.sin, ActivationFunction.reference_sin,
          new FloatValue.Bounds( -1, +1 ), new FloatValue.Bounds( -0.005, +0.005 ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        RELU6: new ActivationFunction.Info( 5, "RELU6",
          tf.relu6, ActivationFunction.reference_relu6,
          new FloatValue.Bounds( 0, 6 ), new FloatValue.Bounds( 0, 6 ), new FloatValue.Bounds( 0, 6 ) ),

        COS: new ActivationFunction.Info( 6, "COS",
          tf.cos, ActivationFunction.reference_cos,
          new FloatValue.Bounds( -1, +1 ),
          new FloatValue.Bounds( -( ( Math.PI / 2 ) + 0.005 ), -( ( Math.PI / 2 ) - 0.005 ) ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        SIGMOID: new ActivationFunction.Info( 7, "SIGMOID",
          tf.sigmoid, ActivationFunction.reference_sigmoid,
          new FloatValue.Bounds( 0, 1 ), new FloatValue.Bounds( -0.125, +0.125 ), new FloatValue.Bounds( +0.468, +0.532 ) ),

        // (2021/12/09)
        // The input linear domain and output range of RELU are [ 0, +Infinity ]. However, it is not so friendly to interact with Infinity
        // because the results may be Infinity or even NaN. This is more obvious for scale-translate into linear domain in PointDepthPoint.
        // So, the RELU is excluded from the activation function list.
        //
        // (2022/01/11)
        // However, if the BoundsArraySet.afterActivation is calculate by .clamp_byXxx() (not by set_byXxx()), the Infinity bounds is
        // not a problem.
        RELU: new ActivationFunction.Info( 8, "RELU",
          tf.relu, ActivationFunction.reference_relu,
          new FloatValue.Bounds( 0, +Infinity ), new FloatValue.Bounds( 0, +Infinity ), new FloatValue.Bounds( 0, +Infinity ) ),

        //SOFTPLUS: new ActivationFunction.Info( 9, "SOFTPLUS",
        //  tf.softplus, ActivationFunction.reference_softplus,
        //  new FloatValue.Bounds( 0, +Infinity ),
        //  new FloatValue.Bounds( +5, +Infinity ), new FloatValue.Bounds( +5, +Infinity ) ),
*/
      }
    );

  }

  /**
   * This non-linear function has the a little smaller range (i.e. 5) than RELU6 (i.e. 7), but has both negative and positive value
   * around zero point.
   */
  static clipByValue_Negative2_Positive2( x ) {
    return tf.clipByValue( x, -2, +2 );
  }

  static reference_clipByValue_Negative2_Positive2( x ) {
    if ( x <= -2 )
      return -2;
    else if ( x >= +2 )
      return +2;
    return x;
  }

  /**
   * This non-linear function has the same range (i.e. 7) as RELU6, but has both negative and positive value around zero point.
   */
  static clipByValue_Negative3_Positive3( x ) {
    return tf.clipByValue( x, -3, +3 );
  }

  static reference_clipByValue_Negative3_Positive3( x ) {
    if ( x <= -3 )
      return -3;
    else if ( x >= +3 )
      return +3;
    return x;
  }

  static reference_tanh( x ) { return Math.tanh( x ); }
  static reference_sin( x )  { return Math.sin( x );  }
  static reference_cos( x )  { return Math.cos( x );  }

  static reference_relu6( x ) {
    if ( x <= 0 )
      return 0;
    else if ( x >= +6 )
      return +6;
    return x;
  }

  static reference_relu( x ) {
    if ( x <= 0 )
      return 0;
    return x;
  }

  static reference_sigmoid( x ) {
    return ( 1 / ( 1 + Math.exp( -x ) ) );
  }

  static reference_softplus( x ) {
    return Math.log( 1 + Math.exp( x ) );
  }

}

/**
 *
 * @member {number} nActivationId
 *   The activation function id (ValueDesc.ActivationFunction.Singleton.Ids.Xxx).
 *
 * @member {Function} pfn
 *   The activation function by tensorflow.js (e.g. tf.relu6, tf.tanh, tf.sin, tf.cos, tf.sigmoid, tf.relu).
 *
 * @member {Function} pfnReference
 *   The activation function by CPU. It is used by NumberImage.Base for comparing correctness.
 *
 * @member {FloatValue.Bounds} outputRange
 *   The output value lower and upper bounds of the activation function for the whole input domain.
 *
 * @member {FloatValue.Bounds} inputDomainLinear
 *   The input value lower and upper bounds of the activation function for keeping the mapping from input to output almost linear. In
 * general speaking, an activation function is non-linear in the whole domain. However, inside this special part of the domain, it looks
 * almost like a linear function.
 *
 * @member {FloatValue.Bounds} outputRangeLinear
 *   The activattion function's output range when its input is in domain this.inputDomainLinear.
 */
ActivationFunction.Info = class ActivationFunction_Info extends Int.Info {
  constructor( nActivationId, nameForMessage, pfn, pfnReference, outputRange, inputDomainLinear, outputRangeLinear ) {
    super( nActivationId, nameForMessage );
    this.pfn = pfn;
    this.pfnReference = pfnReference;
    this.outputRange = outputRange;
    this.inputDomainLinear = inputDomainLinear;
    this.outputRangeLinear = outputRangeLinear;
  }
}

/** The only one ValueDesc.ActivationFunction instance. */
ActivationFunction.Singleton = new ActivationFunction;
<commit_msg>CLIP_BY_VALUE_N2_P2<commit_after>export { ActivationFunction };

import { Int } from "./ValueDesc_Base.js";
import * as Weights from "../Weights.js";
import * as FloatValue from "../FloatValue.js";

/**
 * Describe activation function parameter's id, range, name.
 *
 * The activation function ActivationFunction.Info.pfn( x ) is almost linear when x is inside inputDomainLinear. Its output is
 * outputRangeLinear.
 *
 *   - CLIP_BY_VALUE_N2_P2( [ -2, +2 ] ) = [ -2, +2 ]
 *   - CLIP_BY_VALUE_N3_P3( [ -3, +3 ] ) = [ -3, +3 ]
 *   - RELU6( [ 0, 6 ] ) = [ 0, 6 ]
 *   - TANH ( [ -0.005, +0.005 ] ) = [ -0.005, +0.005]
 *   - SIN  ( [ -0.005, +0.005 ] ) = [ -0.005, +0.005]
 *   - COS  ( [ -( ( PI / 2 ) + 0.005 ), -( ( PI / 2 ) - 0.005 ) ] ) =  [ -0.005, +0.005 ]
 *   - SIGMOID ( [ -0.125, +0.125 ] ) = [ +0.468, +0.532 ]
 *   - RELU ( 0, +Infinity ) = [ 0, +Infinity ]
 *   - SOFTPLUS ( +5, +Infinity ) = [ +5, +Infinity ]
 *
 *
 * Note:
 *   - NONE: Beware. It easily results in infinity value because it does not have upper bound.
 *   - RELU: Beware. It easily results in infinity value because it does not have upper bound.
 *   - SOFTPLUS: Avoid. Backend WASM does not support it.
 *   - ERF: Avoid. Backend WASM does not support it.
 *

//!!! ...unfinished... (2022/07/14)
// Could [ -2, +2 ] be better than [ -3, +3 ]? Does two's power reduce floating-point accumulated error (after ShuffleNetV2_byMobileNetV2
// pass-through by scaling and un-scaling repeately)?

//!!! ...unfinished... (2022/01/11)
// What about: (tf.clipByValue() is also supported by WASM)
//   - tf.clipByValue( -1, +1 )?
//   - tf.clipByValue( -2, +2 )?
//   - tf.clipByValue( -3, +3 )?
//   - tf.clipByValue( -64, +64 )?
//   - tf.clipByValue( -128, +128 )?

 *   - CLIP_BY_VALUE, TANH, SIN, ERF are good if pass-through by only scale (i.e. without translate) is needed. Because the output
 *       range of them includes both negative value and positive value near the origin point. (On the other hand, RELU, RELU6 are
 *       not good for this purpose.)
 *
 *
 *
 * Q1: Since activation by clipByValue() is enough, why not just using saturated integer for convolution?
 * A1: Although saturated integer could achieve clipByValue automatically without activation function (i.e. less computation time).
 *     It has some disadvantages:
 *
 *      - It can not escape saturation (i.e. escape activation) by scaling. The reason is that needs multiply a number less
 *         than one which can not be represented by integer (i.e. floatig-point number is required).
 *
 *      - It may not be saturated (i.e. activated) by bias. When the input value is the minimum value (e.g. -255), adding a
 *         bias (even if adding the maximum value 255) can not saturate it to maximum value (e.g. +255).
 *
 * On the other hand, floating-point number has already been similar to a kind of saturated number (i.e. maximum is saturated to
 * +Infinity, and minimum is saturated to -Infinity).
 *
 *
 *
 * Q2: Why use CLIP_BY_VALUE_N2_P2 instead of CLIP_BY_VALUE_N3_P3?
 * A2: To reduce floating-point accumulated error.
 *
 * CLIP_BY_VALUE_N3_P3:
 *
 *   - Advantage: It has larger inputDomainLinear so that activation-escaping could use a larger scale. This reduces floating-point
 *       truncation error.
 *
 *   - Disadvantage: Its activated value (i.e. 3) seems harder to become any other value by a floating-point finite scaling.
 *       This increases the floating-point accumulated error. For example, in order to let 3 become 100, a scale 33.333...
 *       should be used. However, 33.3 can not be represented by a finite floating-point number.
 *
 * CLIP_BY_VALUE_N2_P2:
 *
 *   - Disadvantage: It has smaller inputDomainLinear [ -2, +2 ] (than CLIP_BY_VALUE_N3_P3 [ -3, +3 ]) so that activation-escaping
 *       needs use a smaller scale. This increases floating-point truncation error. Fortunately, if a just enough input value
 *       bounds is used (e.g. for RGBA image, uses 2^8 (instead of 2^24) as every channel's input value bounds), this issue could
 *       be less problematic.
 *
 *   - Advantage: Its activated value (i.e. 2) is easier to become any other value by a floating-point finite scaling.
 *       This reduces the floating-point accumulated error. In this point of view, not only 2, any powers of two (i.e. 0.25, 0.5, 1,
 *       2, 4, 8, ...) is good enough for this task.
 *
 */
class ActivationFunction extends Int {

  constructor() {

//!!! (2022/07/05 Remarked) For speed-up testing.
//    super( 0, 8,
//      [ "NONE",  "CLIP_BY_VALUE_N2_P2", "CLIP_BY_VALUE_N3_P3", "TANH",  "SIN", "RELU6",  "COS",  "SIGMOID", "RELU" ], //  "SOFTPLUS" ],

    super( 0, 1,
      { NONE : new ActivationFunction.Info( 0, "NONE", null, null, null, null, null ),

        CLIP_BY_VALUE_N2_P2: new ActivationFunction.Info( 1, "CLIP_BY_VALUE_N2_P2",

//!!! (2022/08/04 Temp Remarked) Try large range whether reduceing accumulated error.
          ActivationFunction.clipByValue_Negative2_Positive2,
          ActivationFunction.reference_clipByValue_Negative2_Positive2,
          new FloatValue.Bounds( -2, +2 ), new FloatValue.Bounds( -2, +2 ), new FloatValue.Bounds( -2, +2 ) ),

//!!! (2022/08/04 Temp Remarked) Try small range whether reduceing accumulated error.
          // ActivationFunction.clipByValue_Negative8_Positive8,
          // ActivationFunction.reference_clipByValue_Negative8_Positive8,
          // new FloatValue.Bounds( -8, +8 ), new FloatValue.Bounds( -8, +8 ), new FloatValue.Bounds( -8, +8 ) ),

          // ActivationFunction.clipByValue_Negative4_Positive4,
          // ActivationFunction.reference_clipByValue_Negative4_Positive4,
          // new FloatValue.Bounds( -4, +4 ), new FloatValue.Bounds( -4, +4 ), new FloatValue.Bounds( -4, +4 ) ),

//!!! (2022/07/05 Remarked) For speed-up testing.
/*
        CLIP_BY_VALUE_N3_P3: new ActivationFunction.Info( 2, "CLIP_BY_VALUE_N3_P3",
          ActivationFunction.clipByValue_Negative3_Positive3,
          ActivationFunction.reference_clipByValue_Negative3_Positive3,
          new FloatValue.Bounds( -3, +3 ), new FloatValue.Bounds( -3, +3 ), new FloatValue.Bounds( -3, +3 ) ),

        TANH: new ActivationFunction.Info( 3, "TANH",
          tf.tanh, ActivationFunction.reference_tanh,
          new FloatValue.Bounds( -1, +1 ), new FloatValue.Bounds( -0.005, +0.005 ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        SIN: new ActivationFunction.Info( 4, "SIN",
          tf.sin, ActivationFunction.reference_sin,
          new FloatValue.Bounds( -1, +1 ), new FloatValue.Bounds( -0.005, +0.005 ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        RELU6: new ActivationFunction.Info( 5, "RELU6",
          tf.relu6, ActivationFunction.reference_relu6,
          new FloatValue.Bounds( 0, 6 ), new FloatValue.Bounds( 0, 6 ), new FloatValue.Bounds( 0, 6 ) ),

        COS: new ActivationFunction.Info( 6, "COS",
          tf.cos, ActivationFunction.reference_cos,
          new FloatValue.Bounds( -1, +1 ),
          new FloatValue.Bounds( -( ( Math.PI / 2 ) + 0.005 ), -( ( Math.PI / 2 ) - 0.005 ) ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        SIGMOID: new ActivationFunction.Info( 7, "SIGMOID",
          tf.sigmoid, ActivationFunction.reference_sigmoid,
          new FloatValue.Bounds( 0, 1 ), new FloatValue.Bounds( -0.125, +0.125 ), new FloatValue.Bounds( +0.468, +0.532 ) ),

        // (2021/12/09)
        // The input linear domain and output range of RELU are [ 0, +Infinity ]. However, it is not so friendly to interact with Infinity
        // because the results may be Infinity or even NaN. This is more obvious for scale-translate into linear domain in PointDepthPoint.
        // So, the RELU is excluded from the activation function list.
        //
        // (2022/01/11)
        // However, if the BoundsArraySet.afterActivation is calculate by .clamp_byXxx() (not by set_byXxx()), the Infinity bounds is
        // not a problem.
        RELU: new ActivationFunction.Info( 8, "RELU",
          tf.relu, ActivationFunction.reference_relu,
          new FloatValue.Bounds( 0, +Infinity ), new FloatValue.Bounds( 0, +Infinity ), new FloatValue.Bounds( 0, +Infinity ) ),

        //SOFTPLUS: new ActivationFunction.Info( 9, "SOFTPLUS",
        //  tf.softplus, ActivationFunction.reference_softplus,
        //  new FloatValue.Bounds( 0, +Infinity ),
        //  new FloatValue.Bounds( +5, +Infinity ), new FloatValue.Bounds( +5, +Infinity ) ),
*/
      }
    );

  }

  /**   */
  static reference_clipByValue_lowerBound_upperBound( x, lowerBound, upperBound ) {
    if ( x < lowerBound )
      return lowerBound;
    else if ( x > upperBound )
      return upperBound;
    return x;
  }

  /** */
  static clipByValue_Negative16_Positive16( x ) { return tf.clipByValue( x, -16, +16 ); }
  static reference_clipByValue_Negative16_Positive16( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -16, +16 );
  }

  /** */
  static clipByValue_Negative8_Positive8( x ) { return tf.clipByValue( x, -8, +8 ); }
  static reference_clipByValue_Negative8_Positive8( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -8, +8 );
  }

  /** */
  static clipByValue_Negative4_Positive4( x ) { return tf.clipByValue( x, -4, +4 ); }
  static reference_clipByValue_Negative4_Positive4( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -4, +4 );
  }

  /**
   * This non-linear function has the a little smaller range (i.e. 5) than RELU6 (i.e. 7), but has both negative and positive value
   * around zero point.
   */
  static clipByValue_Negative2_Positive2( x ) { return tf.clipByValue( x, -2, +2 ); }
  static reference_clipByValue_Negative2_Positive2( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -2, +2 );
  }

  /**
   * This non-linear function has the same range (i.e. 7) as RELU6, but has both negative and positive value around zero point.
   */
  static clipByValue_Negative3_Positive3( x ) { return tf.clipByValue( x, -3, +3 ); }
  static reference_clipByValue_Negative3_Positive3( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -3, +3 );
  }

  static reference_tanh( x ) { return Math.tanh( x ); }
  static reference_sin( x )  { return Math.sin( x );  }
  static reference_cos( x )  { return Math.cos( x );  }

  static reference_relu6( x ) {
    if ( x <= 0 )
      return 0;
    else if ( x >= +6 )
      return +6;
    return x;
  }

  static reference_relu( x ) {
    if ( x <= 0 )
      return 0;
    return x;
  }

  static reference_sigmoid( x ) {
    return ( 1 / ( 1 + Math.exp( -x ) ) );
  }

  static reference_softplus( x ) {
    return Math.log( 1 + Math.exp( x ) );
  }

}

/**
 *
 * @member {number} nActivationId
 *   The activation function id (ValueDesc.ActivationFunction.Singleton.Ids.Xxx).
 *
 * @member {Function} pfn
 *   The activation function by tensorflow.js (e.g. tf.relu6, tf.tanh, tf.sin, tf.cos, tf.sigmoid, tf.relu).
 *
 * @member {Function} pfnReference
 *   The activation function by CPU. It is used by NumberImage.Base for comparing correctness.
 *
 * @member {FloatValue.Bounds} outputRange
 *   The output value lower and upper bounds of the activation function for the whole input domain.
 *
 * @member {FloatValue.Bounds} inputDomainLinear
 *   The input value lower and upper bounds of the activation function for keeping the mapping from input to output almost linear. In
 * general speaking, an activation function is non-linear in the whole domain. However, inside this special part of the domain, it looks
 * almost like a linear function.
 *
 * @member {FloatValue.Bounds} outputRangeLinear
 *   The activattion function's output range when its input is in domain this.inputDomainLinear.
 */
ActivationFunction.Info = class ActivationFunction_Info extends Int.Info {
  constructor( nActivationId, nameForMessage, pfn, pfnReference, outputRange, inputDomainLinear, outputRangeLinear ) {
    super( nActivationId, nameForMessage );
    this.pfn = pfn;
    this.pfnReference = pfnReference;
    this.outputRange = outputRange;
    this.inputDomainLinear = inputDomainLinear;
    this.outputRangeLinear = outputRangeLinear;
  }
}

/** The only one ValueDesc.ActivationFunction instance. */
ActivationFunction.Singleton = new ActivationFunction;
<|endoftext|>


<commit_before>export { ActivationFunction };

import { Int } from "./ValueDesc_Base.js";
import * as Weights from "../Weights.js";
import * as FloatValue from "../FloatValue.js";

/**
 * Describe activation function parameter's id, range, name.
 *
 * The activation function ActivationFunction.Info.pfn( x ) is almost linear when x is inside inputDomainLinear. Its output is
 * outputRangeLinear.
 *
 *   - CLIP_BY_VALUE_N2_P2( [ -2, +2 ] ) = [ -2, +2 ]
 *   - CLIP_BY_VALUE_N3_P3( [ -3, +3 ] ) = [ -3, +3 ]
 *   - RELU6( [ 0, 6 ] ) = [ 0, 6 ]
 *   - TANH ( [ -0.005, +0.005 ] ) = [ -0.005, +0.005]
 *   - SIN  ( [ -0.005, +0.005 ] ) = [ -0.005, +0.005]
 *   - COS  ( [ -( ( PI / 2 ) + 0.005 ), -( ( PI / 2 ) - 0.005 ) ] ) =  [ -0.005, +0.005 ]
 *   - SIGMOID ( [ -0.125, +0.125 ] ) = [ +0.468, +0.532 ]
 *   - RELU ( 0, +Infinity ) = [ 0, +Infinity ]
 *   - SOFTPLUS ( +5, +Infinity ) = [ +5, +Infinity ]
 *
 *
 * Note:
 *   - NONE: Beware. It easily results in infinity value because it does not have upper bound.
 *   - RELU: Beware. It easily results in infinity value because it does not have upper bound.
 *   - SOFTPLUS: Avoid. Backend WASM does not support it.
 *   - ERF: Avoid. Backend WASM does not support it.
 *

//!!! ...unfinished... (2022/07/14)
// Could [ -2, +2 ] be better than [ -3, +3 ]? Does two's power reduce floating-point accumulated error (after ShuffleNetV2_byMobileNetV2
// pass-through by scaling and un-scaling repeately)?

//!!! ...unfinished... (2022/01/11)
// What about: (tf.clipByValue() is also supported by WASM)
//   - tf.clipByValue( -1, +1 )?
//   - tf.clipByValue( -2, +2 )?
//   - tf.clipByValue( -3, +3 )?
//   - tf.clipByValue( -64, +64 )?
//   - tf.clipByValue( -128, +128 )?

 *   - CLIP_BY_VALUE, TANH, SIN, ERF are good if pass-through by only scale (i.e. without translate) is needed. Because the output
 *       range of them includes both negative value and positive value near the origin point. (On the other hand, RELU, RELU6 are
 *       not good for this purpose.)
 *
 *
 *
 * Q1: Since activation by clipByValue() is enough, why not just using saturated integer for convolution?
 * A1: Although saturated integer could achieve clipByValue automatically without activation function (i.e. less computation time).
 *     It has some disadvantages:
 *
 *      - It can not escape saturation (i.e. escape activation) by scaling. The reason is that needs multiply a number less
 *         than one which can not be represented by integer (i.e. floatig-point number is required).
 *
 *      - It may not be saturated (i.e. activated) by bias. When the input value is the minimum value (e.g. -255), adding a
 *         bias (even if adding the maximum value 255) can not saturate it to maximum value (e.g. +255).
 *
 * On the other hand, floating-point number has already been similar to a kind of saturated number (i.e. maximum is saturated to
 * +Infinity, and minimum is saturated to -Infinity).
 *
 *
 *
 * Q2: Why use CLIP_BY_VALUE_N2_P2 instead of CLIP_BY_VALUE_N3_P3?
 * A2: To reduce floating-point accumulated error.
 *
 * CLIP_BY_VALUE_N3_P3:
 *
 *   - Advantage: It has larger inputDomainLinear so that activation-escaping could use a larger scale. This reduces floating-point
 *       truncation error.
 *
 *   - Disadvantage: Its activated value (i.e. 3) seems harder to become any other value by a floating-point finite scaling.
 *       This increases the floating-point accumulated error. For example, in order to let 3 become 100, a scale 33.333...
 *       should be used. However, 33.3 can not be represented by a finite floating-point number.
 *
 * CLIP_BY_VALUE_N2_P2:
 *
 *   - Disadvantage: It has smaller inputDomainLinear [ -2, +2 ] (than CLIP_BY_VALUE_N3_P3 [ -3, +3 ]) so that activation-escaping
 *       needs use a smaller scale. This increases floating-point truncation error. Fortunately, if a just enough input value
 *       bounds is used (e.g. for RGBA image, uses 2^8 (instead of 2^24) as every channel's input value bounds), this issue could
 *       be less problematic.
 *
 *   - Advantage: Its activated value (i.e. 2) is easier to become any other value by a floating-point finite scaling.
 *       This reduces the floating-point accumulated error. In this point of view, not only 2, any powers of two (i.e. 0.25, 0.5, 1,
 *       2, 4, 8, ...) is good enough for this task.
 *
 */
class ActivationFunction extends Int {

  constructor() {

//!!! (2022/07/05 Remarked) For speed-up testing.
//    super( 0, 8,
//      [ "NONE",  "CLIP_BY_VALUE_N2_P2", "CLIP_BY_VALUE_N3_P3", "TANH",  "SIN", "RELU6",  "COS",  "SIGMOID", "RELU" ], //  "SOFTPLUS" ],

    super( 0, 1,
      { NONE : new ActivationFunction.Info( 0, "NONE", null, null, null, null, null ),

        CLIP_BY_VALUE_N2_P2: new ActivationFunction.Info( 1, "CLIP_BY_VALUE_N2_P2",

//!!! (2022/08/04 Temp Remarked) Try large range whether reduceing accumulated error.
          // ActivationFunction.clipByValue_Negative2_Positive2,
          // ActivationFunction.reference_clipByValue_Negative2_Positive2,
          // new FloatValue.Bounds( -2, +2 ), new FloatValue.Bounds( -2, +2 ), new FloatValue.Bounds( -2, +2 ) ),

//!!! (2022/08/04 Temp Remarked) Try small range whether reduceing accumulated error.
          // ActivationFunction.clipByValue_Negative8_Positive8,
          // ActivationFunction.reference_clipByValue_Negative8_Positive8,
          // new FloatValue.Bounds( -8, +8 ), new FloatValue.Bounds( -8, +8 ), new FloatValue.Bounds( -8, +8 ) ),

          ActivationFunction.clipByValue_Negative4_Positive4,
          ActivationFunction.reference_clipByValue_Negative4_Positive4,
          new FloatValue.Bounds( -4, +4 ), new FloatValue.Bounds( -4, +4 ), new FloatValue.Bounds( -4, +4 ) ),

//!!! (2022/07/05 Remarked) For speed-up testing.
/*
        CLIP_BY_VALUE_N3_P3: new ActivationFunction.Info( 2, "CLIP_BY_VALUE_N3_P3",
          ActivationFunction.clipByValue_Negative3_Positive3,
          ActivationFunction.reference_clipByValue_Negative3_Positive3,
          new FloatValue.Bounds( -3, +3 ), new FloatValue.Bounds( -3, +3 ), new FloatValue.Bounds( -3, +3 ) ),

        TANH: new ActivationFunction.Info( 3, "TANH",
          tf.tanh, ActivationFunction.reference_tanh,
          new FloatValue.Bounds( -1, +1 ), new FloatValue.Bounds( -0.005, +0.005 ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        SIN: new ActivationFunction.Info( 4, "SIN",
          tf.sin, ActivationFunction.reference_sin,
          new FloatValue.Bounds( -1, +1 ), new FloatValue.Bounds( -0.005, +0.005 ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        RELU6: new ActivationFunction.Info( 5, "RELU6",
          tf.relu6, ActivationFunction.reference_relu6,
          new FloatValue.Bounds( 0, 6 ), new FloatValue.Bounds( 0, 6 ), new FloatValue.Bounds( 0, 6 ) ),

        COS: new ActivationFunction.Info( 6, "COS",
          tf.cos, ActivationFunction.reference_cos,
          new FloatValue.Bounds( -1, +1 ),
          new FloatValue.Bounds( -( ( Math.PI / 2 ) + 0.005 ), -( ( Math.PI / 2 ) - 0.005 ) ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        SIGMOID: new ActivationFunction.Info( 7, "SIGMOID",
          tf.sigmoid, ActivationFunction.reference_sigmoid,
          new FloatValue.Bounds( 0, 1 ), new FloatValue.Bounds( -0.125, +0.125 ), new FloatValue.Bounds( +0.468, +0.532 ) ),

        // (2021/12/09)
        // The input linear domain and output range of RELU are [ 0, +Infinity ]. However, it is not so friendly to interact with Infinity
        // because the results may be Infinity or even NaN. This is more obvious for scale-translate into linear domain in PointDepthPoint.
        // So, the RELU is excluded from the activation function list.
        //
        // (2022/01/11)
        // However, if the BoundsArraySet.afterActivation is calculate by .clamp_byXxx() (not by set_byXxx()), the Infinity bounds is
        // not a problem.
        RELU: new ActivationFunction.Info( 8, "RELU",
          tf.relu, ActivationFunction.reference_relu,
          new FloatValue.Bounds( 0, +Infinity ), new FloatValue.Bounds( 0, +Infinity ), new FloatValue.Bounds( 0, +Infinity ) ),

        //SOFTPLUS: new ActivationFunction.Info( 9, "SOFTPLUS",
        //  tf.softplus, ActivationFunction.reference_softplus,
        //  new FloatValue.Bounds( 0, +Infinity ),
        //  new FloatValue.Bounds( +5, +Infinity ), new FloatValue.Bounds( +5, +Infinity ) ),
*/
      }
    );

  }

  /**   */
  static reference_clipByValue_lowerBound_upperBound( x, lowerBound, upperBound ) {
    if ( x < lowerBound )
      return lowerBound;
    else if ( x > upperBound )
      return upperBound;
    return x;
  }

  /** */
  static clipByValue_Negative16_Positive16( x ) { return tf.clipByValue( x, -16, +16 ); }
  static reference_clipByValue_Negative16_Positive16( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -16, +16 );
  }

  /** */
  static clipByValue_Negative8_Positive8( x ) { return tf.clipByValue( x, -8, +8 ); }
  static reference_clipByValue_Negative8_Positive8( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -8, +8 );
  }

  /** */
  static clipByValue_Negative4_Positive4( x ) { return tf.clipByValue( x, -4, +4 ); }
  static reference_clipByValue_Negative4_Positive4( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -4, +4 );
  }

  /**
   * This non-linear function has the a little smaller range (i.e. 5) than RELU6 (i.e. 7), but has both negative and positive value
   * around zero point.
   */
  static clipByValue_Negative2_Positive2( x ) { return tf.clipByValue( x, -2, +2 ); }
  static reference_clipByValue_Negative2_Positive2( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -2, +2 );
  }

  /**
   * This non-linear function has the same range (i.e. 7) as RELU6, but has both negative and positive value around zero point.
   */
  static clipByValue_Negative3_Positive3( x ) { return tf.clipByValue( x, -3, +3 ); }
  static reference_clipByValue_Negative3_Positive3( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -3, +3 );
  }

  static reference_tanh( x ) { return Math.tanh( x ); }
  static reference_sin( x )  { return Math.sin( x );  }
  static reference_cos( x )  { return Math.cos( x );  }

  static reference_relu6( x ) {
    if ( x <= 0 )
      return 0;
    else if ( x >= +6 )
      return +6;
    return x;
  }

  static reference_relu( x ) {
    if ( x <= 0 )
      return 0;
    return x;
  }

  static reference_sigmoid( x ) {
    return ( 1 / ( 1 + Math.exp( -x ) ) );
  }

  static reference_softplus( x ) {
    return Math.log( 1 + Math.exp( x ) );
  }

}

/**
 *
 * @member {number} nActivationId
 *   The activation function id (ValueDesc.ActivationFunction.Singleton.Ids.Xxx).
 *
 * @member {Function} pfn
 *   The activation function by tensorflow.js (e.g. tf.relu6, tf.tanh, tf.sin, tf.cos, tf.sigmoid, tf.relu).
 *
 * @member {Function} pfnReference
 *   The activation function by CPU. It is used by NumberImage.Base for comparing correctness.
 *
 * @member {FloatValue.Bounds} outputRange
 *   The output value lower and upper bounds of the activation function for the whole input domain.
 *
 * @member {FloatValue.Bounds} inputDomainLinear
 *   The input value lower and upper bounds of the activation function for keeping the mapping from input to output almost linear. In
 * general speaking, an activation function is non-linear in the whole domain. However, inside this special part of the domain, it looks
 * almost like a linear function.
 *
 * @member {FloatValue.Bounds} outputRangeLinear
 *   The activattion function's output range when its input is in domain this.inputDomainLinear.
 */
ActivationFunction.Info = class ActivationFunction_Info extends Int.Info {
  constructor( nActivationId, nameForMessage, pfn, pfnReference, outputRange, inputDomainLinear, outputRangeLinear ) {
    super( nActivationId, nameForMessage );
    this.pfn = pfn;
    this.pfnReference = pfnReference;
    this.outputRange = outputRange;
    this.inputDomainLinear = inputDomainLinear;
    this.outputRangeLinear = outputRangeLinear;
  }
}

/** The only one ValueDesc.ActivationFunction instance. */
ActivationFunction.Singleton = new ActivationFunction;
<commit_msg>clipByValue_Negative2_Positive2<commit_after>export { ActivationFunction };

import { Int } from "./ValueDesc_Base.js";
import * as Weights from "../Weights.js";
import * as FloatValue from "../FloatValue.js";

/**
 * Describe activation function parameter's id, range, name.
 *
 * The activation function ActivationFunction.Info.pfn( x ) is almost linear when x is inside inputDomainLinear. Its output is
 * outputRangeLinear.
 *
 *   - CLIP_BY_VALUE_N2_P2( [ -2, +2 ] ) = [ -2, +2 ]
 *   - CLIP_BY_VALUE_N3_P3( [ -3, +3 ] ) = [ -3, +3 ]
 *   - RELU6( [ 0, 6 ] ) = [ 0, 6 ]
 *   - TANH ( [ -0.005, +0.005 ] ) = [ -0.005, +0.005]
 *   - SIN  ( [ -0.005, +0.005 ] ) = [ -0.005, +0.005]
 *   - COS  ( [ -( ( PI / 2 ) + 0.005 ), -( ( PI / 2 ) - 0.005 ) ] ) =  [ -0.005, +0.005 ]
 *   - SIGMOID ( [ -0.125, +0.125 ] ) = [ +0.468, +0.532 ]
 *   - RELU ( 0, +Infinity ) = [ 0, +Infinity ]
 *   - SOFTPLUS ( +5, +Infinity ) = [ +5, +Infinity ]
 *
 *
 * Note:
 *   - NONE: Beware. It easily results in infinity value because it does not have upper bound.
 *   - RELU: Beware. It easily results in infinity value because it does not have upper bound.
 *   - SOFTPLUS: Avoid. Backend WASM does not support it.
 *   - ERF: Avoid. Backend WASM does not support it.
 *

//!!! ...unfinished... (2022/07/14)
// Could [ -2, +2 ] be better than [ -3, +3 ]? Does two's power reduce floating-point accumulated error (after ShuffleNetV2_byMobileNetV2
// pass-through by scaling and un-scaling repeately)?

//!!! ...unfinished... (2022/01/11)
// What about: (tf.clipByValue() is also supported by WASM)
//   - tf.clipByValue( -1, +1 )?
//   - tf.clipByValue( -2, +2 )?
//   - tf.clipByValue( -3, +3 )?
//   - tf.clipByValue( -64, +64 )?
//   - tf.clipByValue( -128, +128 )?

 *   - CLIP_BY_VALUE, TANH, SIN, ERF are good if pass-through by only scale (i.e. without translate) is needed. Because the output
 *       range of them includes both negative value and positive value near the origin point. (On the other hand, RELU, RELU6 are
 *       not good for this purpose.)
 *
 *
 *
 * Q1: Since activation by clipByValue() is enough, why not just using saturated integer for convolution?
 * A1: Although saturated integer could achieve clipByValue automatically without activation function (i.e. less computation time).
 *     It has some disadvantages:
 *
 *      - It can not escape saturation (i.e. escape activation) by scaling. The reason is that needs multiply a number less
 *         than one which can not be represented by integer (i.e. floatig-point number is required).
 *
 *      - It may not be saturated (i.e. activated) by bias. When the input value is the minimum value (e.g. -255), adding a
 *         bias (even if adding the maximum value 255) can not saturate it to maximum value (e.g. +255).
 *
 * On the other hand, floating-point number has already been similar to a kind of saturated number (i.e. maximum is saturated to
 * +Infinity, and minimum is saturated to -Infinity).
 *
 *
 *
 * Q2: Why use CLIP_BY_VALUE_N2_P2 instead of CLIP_BY_VALUE_N3_P3?
 * A2: To reduce floating-point accumulated error.
 *
 * CLIP_BY_VALUE_N3_P3:
 *
 *   - Advantage: It has larger inputDomainLinear so that activation-escaping could use a larger scale. This reduces floating-point
 *       truncation error.
 *
 *   - Disadvantage: Its activated value (i.e. 3) seems harder to become any other value by a floating-point finite scaling.
 *       This increases the floating-point accumulated error. For example, in order to let 3 become 100, a scale 33.333...
 *       should be used. However, 33.3 can not be represented by a finite floating-point number.
 *
 * CLIP_BY_VALUE_N2_P2:
 *
 *   - Disadvantage: It has smaller inputDomainLinear [ -2, +2 ] (than CLIP_BY_VALUE_N3_P3 [ -3, +3 ]) so that activation-escaping
 *       needs use a smaller scale. This increases floating-point truncation error. Fortunately, if a just enough input value
 *       bounds is used (e.g. for RGBA image, uses 2^8 (instead of 2^24) as every channel's input value bounds), this issue could
 *       be less problematic.
 *
 *   - Advantage: Its activated value (i.e. 2) is easier to become any other value by a floating-point finite scaling.
 *       This reduces the floating-point accumulated error. In this point of view, not only 2, any powers of two (i.e. 0.25, 0.5, 1,
 *       2, 4, 8, ...) is good enough for this task.
 *
 */
class ActivationFunction extends Int {

  constructor() {

//!!! (2022/07/05 Remarked) For speed-up testing.
//    super( 0, 8,
//      [ "NONE",  "CLIP_BY_VALUE_N2_P2", "CLIP_BY_VALUE_N3_P3", "TANH",  "SIN", "RELU6",  "COS",  "SIGMOID", "RELU" ], //  "SOFTPLUS" ],

    super( 0, 1,
      { NONE : new ActivationFunction.Info( 0, "NONE", null, null, null, null, null ),

        CLIP_BY_VALUE_N2_P2: new ActivationFunction.Info( 1, "CLIP_BY_VALUE_N2_P2",

//!!! (2022/08/04 Temp Remarked) Try large range whether reduceing accumulated error.
          ActivationFunction.clipByValue_Negative2_Positive2,
          ActivationFunction.reference_clipByValue_Negative2_Positive2,
          new FloatValue.Bounds( -2, +2 ), new FloatValue.Bounds( -2, +2 ), new FloatValue.Bounds( -2, +2 ) ),

//!!! (2022/08/04 Temp Remarked) Try small range whether reduceing accumulated error.
          // ActivationFunction.clipByValue_Negative8_Positive8,
          // ActivationFunction.reference_clipByValue_Negative8_Positive8,
          // new FloatValue.Bounds( -8, +8 ), new FloatValue.Bounds( -8, +8 ), new FloatValue.Bounds( -8, +8 ) ),

          // ActivationFunction.clipByValue_Negative4_Positive4,
          // ActivationFunction.reference_clipByValue_Negative4_Positive4,
          // new FloatValue.Bounds( -4, +4 ), new FloatValue.Bounds( -4, +4 ), new FloatValue.Bounds( -4, +4 ) ),

//!!! (2022/07/05 Remarked) For speed-up testing.
/*
        CLIP_BY_VALUE_N3_P3: new ActivationFunction.Info( 2, "CLIP_BY_VALUE_N3_P3",
          ActivationFunction.clipByValue_Negative3_Positive3,
          ActivationFunction.reference_clipByValue_Negative3_Positive3,
          new FloatValue.Bounds( -3, +3 ), new FloatValue.Bounds( -3, +3 ), new FloatValue.Bounds( -3, +3 ) ),

        TANH: new ActivationFunction.Info( 3, "TANH",
          tf.tanh, ActivationFunction.reference_tanh,
          new FloatValue.Bounds( -1, +1 ), new FloatValue.Bounds( -0.005, +0.005 ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        SIN: new ActivationFunction.Info( 4, "SIN",
          tf.sin, ActivationFunction.reference_sin,
          new FloatValue.Bounds( -1, +1 ), new FloatValue.Bounds( -0.005, +0.005 ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        RELU6: new ActivationFunction.Info( 5, "RELU6",
          tf.relu6, ActivationFunction.reference_relu6,
          new FloatValue.Bounds( 0, 6 ), new FloatValue.Bounds( 0, 6 ), new FloatValue.Bounds( 0, 6 ) ),

        COS: new ActivationFunction.Info( 6, "COS",
          tf.cos, ActivationFunction.reference_cos,
          new FloatValue.Bounds( -1, +1 ),
          new FloatValue.Bounds( -( ( Math.PI / 2 ) + 0.005 ), -( ( Math.PI / 2 ) - 0.005 ) ), new FloatValue.Bounds( -0.005, +0.005 ) ),

        SIGMOID: new ActivationFunction.Info( 7, "SIGMOID",
          tf.sigmoid, ActivationFunction.reference_sigmoid,
          new FloatValue.Bounds( 0, 1 ), new FloatValue.Bounds( -0.125, +0.125 ), new FloatValue.Bounds( +0.468, +0.532 ) ),

        // (2021/12/09)
        // The input linear domain and output range of RELU are [ 0, +Infinity ]. However, it is not so friendly to interact with Infinity
        // because the results may be Infinity or even NaN. This is more obvious for scale-translate into linear domain in PointDepthPoint.
        // So, the RELU is excluded from the activation function list.
        //
        // (2022/01/11)
        // However, if the BoundsArraySet.afterActivation is calculate by .clamp_byXxx() (not by set_byXxx()), the Infinity bounds is
        // not a problem.
        RELU: new ActivationFunction.Info( 8, "RELU",
          tf.relu, ActivationFunction.reference_relu,
          new FloatValue.Bounds( 0, +Infinity ), new FloatValue.Bounds( 0, +Infinity ), new FloatValue.Bounds( 0, +Infinity ) ),

        //SOFTPLUS: new ActivationFunction.Info( 9, "SOFTPLUS",
        //  tf.softplus, ActivationFunction.reference_softplus,
        //  new FloatValue.Bounds( 0, +Infinity ),
        //  new FloatValue.Bounds( +5, +Infinity ), new FloatValue.Bounds( +5, +Infinity ) ),
*/
      }
    );

  }

  /**   */
  static reference_clipByValue_lowerBound_upperBound( x, lowerBound, upperBound ) {
    if ( x < lowerBound )
      return lowerBound;
    else if ( x > upperBound )
      return upperBound;
    return x;
  }

  /** */
  static clipByValue_Negative16_Positive16( x ) { return tf.clipByValue( x, -16, +16 ); }
  static reference_clipByValue_Negative16_Positive16( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -16, +16 );
  }

  /** */
  static clipByValue_Negative8_Positive8( x ) { return tf.clipByValue( x, -8, +8 ); }
  static reference_clipByValue_Negative8_Positive8( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -8, +8 );
  }

  /** */
  static clipByValue_Negative4_Positive4( x ) { return tf.clipByValue( x, -4, +4 ); }
  static reference_clipByValue_Negative4_Positive4( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -4, +4 );
  }

  /**
   * This non-linear function has the a little smaller range (i.e. 5) than RELU6 (i.e. 7), but has both negative and positive value
   * around zero point.
   */
  static clipByValue_Negative2_Positive2( x ) { return tf.clipByValue( x, -2, +2 ); }
  static reference_clipByValue_Negative2_Positive2( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -2, +2 );
  }

  /**
   * This non-linear function has the same range (i.e. 7) as RELU6, but has both negative and positive value around zero point.
   */
  static clipByValue_Negative3_Positive3( x ) { return tf.clipByValue( x, -3, +3 ); }
  static reference_clipByValue_Negative3_Positive3( x ) {
    return ActivationFunction.reference_clipByValue_lowerBound_upperBound( x, -3, +3 );
  }

  static reference_tanh( x ) { return Math.tanh( x ); }
  static reference_sin( x )  { return Math.sin( x );  }
  static reference_cos( x )  { return Math.cos( x );  }

  static reference_relu6( x ) {
    if ( x <= 0 )
      return 0;
    else if ( x >= +6 )
      return +6;
    return x;
  }

  static reference_relu( x ) {
    if ( x <= 0 )
      return 0;
    return x;
  }

  static reference_sigmoid( x ) {
    return ( 1 / ( 1 + Math.exp( -x ) ) );
  }

  static reference_softplus( x ) {
    return Math.log( 1 + Math.exp( x ) );
  }

}

/**
 *
 * @member {number} nActivationId
 *   The activation function id (ValueDesc.ActivationFunction.Singleton.Ids.Xxx).
 *
 * @member {Function} pfn
 *   The activation function by tensorflow.js (e.g. tf.relu6, tf.tanh, tf.sin, tf.cos, tf.sigmoid, tf.relu).
 *
 * @member {Function} pfnReference
 *   The activation function by CPU. It is used by NumberImage.Base for comparing correctness.
 *
 * @member {FloatValue.Bounds} outputRange
 *   The output value lower and upper bounds of the activation function for the whole input domain.
 *
 * @member {FloatValue.Bounds} inputDomainLinear
 *   The input value lower and upper bounds of the activation function for keeping the mapping from input to output almost linear. In
 * general speaking, an activation function is non-linear in the whole domain. However, inside this special part of the domain, it looks
 * almost like a linear function.
 *
 * @member {FloatValue.Bounds} outputRangeLinear
 *   The activattion function's output range when its input is in domain this.inputDomainLinear.
 */
ActivationFunction.Info = class ActivationFunction_Info extends Int.Info {
  constructor( nActivationId, nameForMessage, pfn, pfnReference, outputRange, inputDomainLinear, outputRangeLinear ) {
    super( nActivationId, nameForMessage );
    this.pfn = pfn;
    this.pfnReference = pfnReference;
    this.outputRange = outputRange;
    this.inputDomainLinear = inputDomainLinear;
    this.outputRangeLinear = outputRangeLinear;
  }
}

/** The only one ValueDesc.ActivationFunction instance. */
ActivationFunction.Singleton = new ActivationFunction;
<|endoftext|>


<commit_before>var CircuitBreaker = function(opts) {
  opts = opts || {};

  this.windowDuration  = opts.windowDuration  || 10000; // milliseconds
  this.numBuckets      = opts.numBuckets      || 10;    // number
  this.timeoutDuration = opts.timeoutDuration || 3000;  // milliseconds
  this.errorThreshold  = opts.errorThreshold  || 50;    // percentage
  this.volumeThreshold = opts.volumeThreshold || 5;     // number
  this.onCircuitOpen   = opts.onCircuitOpen   || function() {};
  this.onCircuitClose   = opts.onCircuitClose || function() {};

  this._buckets = [this._createBucket()];
  this._state = CircuitBreaker.CLOSED;

  this._startTicker();
};

CircuitBreaker.OPEN = 0;
CircuitBreaker.HALF_OPEN = 1;
CircuitBreaker.CLOSED = 2;

// PUBLIC

CircuitBreaker.prototype.run = function(command, fallback) {
  if (this.isOpen()) {
    this._executeFallback(fallback || function() {});
  }
  else {
    this._executeCommand(command);
  }
};

CircuitBreaker.prototype.forceClose = function() {
  this._forced = this._state;
  this._state = CircuitBreaker.CLOSED;
};

CircuitBreaker.prototype.forceOpen = function() {
  this._forced = this._state;
  this._state = CircuitBreaker.OPEN;
};

CircuitBreaker.prototype.unforce = function() {
  this._state = this._forced;
  this._forced = false;
};

CircuitBreaker.prototype.isOpen = function() {
  return this._state == CircuitBreaker.OPEN;
};

// PRIVATE

CircuitBreaker.prototype._startTicker = function() {
  var self = this;
  var bucketIndex = 0;
  var bucketDuration = this.windowDuration / this.numBuckets;

  var tick = function() {
    if (self._buckets.length > self.numBuckets) {
      self._buckets.shift();
    }

    bucketIndex++;

    if (bucketIndex > self.numBuckets) {
      bucketIndex = 0;

      if (self.isOpen()) {
        self._state = CircuitBreaker.HALF_OPEN;
      }
    }

    self._buckets.push(self._createBucket());
  };

  window.setInterval(tick, bucketDuration);
};

CircuitBreaker.prototype._createBucket = function() {
  return { failures: 0, successes: 0, timeouts: 0, shortCircuits: 0 };
};

CircuitBreaker.prototype._lastBucket = function() {
  return this._buckets[this._buckets.length - 1];
};

CircuitBreaker.prototype._executeCommand = function(command) {
  var self = this;
  var timeout;

  var increment = function(prop) {
    return function() {
      if (!timeout) { return; }

      var bucket = self._lastBucket();
      bucket[prop]++;

      if (!self._forced) {
        self._updateState();
      }

      window.clearTimeout(timeout);
      timeout = null;
    }
  };

  timeout = window.setTimeout(increment('timeouts'), this.timeoutDuration);

  command(increment('successes'), increment('failures'));
};

CircuitBreaker.prototype._executeFallback = function(fallback) {
  fallback();

  var bucket = this._lastBucket();
  bucket.shortCircuits++;
};

CircuitBreaker.prototype._calculateMetrics = function() {
  var totalCount = 0, errorCount = 0, errorPercentage = 0;

  for (var i = 0, l = this._buckets.length; i < l; i++) {
    var bucket = this._buckets[i];
    var errors = (bucket.failures + bucket.timeouts);

    errorCount += errors;
    totalCount += (errors + bucket.successes);
  }

  errorPercentage = (errorCount / (totalCount > 0 ? totalCount : 1)) * 100;

  return { totalCount: totalCount, errorCount: errorCount, errorPercentage: errorPercentage };
}

CircuitBreaker.prototype._updateState = function() {
  var metrics = this._calculateMetrics();

  if (this._state == CircuitBreaker.HALF_OPEN) {
    var lastCommandFailed = !this._lastBucket().successes && metrics.errorCount > 0;

    if (lastCommandFailed) {
      this._state = CircuitBreaker.OPEN;
    }
    else {
      this._state = CircuitBreaker.CLOSED;
      this.onCircuitClose();
    }
  }
  else {
    var overErrorThreshold = metrics.errorPercentage > this.errorThreshold;
    var overVolumeThreshold = metrics.totalCount > this.volumeThreshold;
    var overThreshold = overVolumeThreshold && overErrorThreshold;

    if (overThreshold) {
      this._state = CircuitBreaker.OPEN;
      this.onCircuitOpen();
    }
  }
};
<commit_msg>add commonjs support<commit_after>(function() {
  var CircuitBreaker = function(opts) {
    opts = opts || {};

    this.windowDuration  = opts.windowDuration  || 10000; // milliseconds
    this.numBuckets      = opts.numBuckets      || 10;    // number
    this.timeoutDuration = opts.timeoutDuration || 3000;  // milliseconds
    this.errorThreshold  = opts.errorThreshold  || 50;    // percentage
    this.volumeThreshold = opts.volumeThreshold || 5;     // number
    this.onCircuitOpen   = opts.onCircuitOpen   || function() {};
    this.onCircuitClose   = opts.onCircuitClose || function() {};

    this._buckets = [this._createBucket()];
    this._state = CircuitBreaker.CLOSED;

    this._startTicker();
  };

  CircuitBreaker.OPEN = 0;
  CircuitBreaker.HALF_OPEN = 1;
  CircuitBreaker.CLOSED = 2;

  // PUBLIC

  CircuitBreaker.prototype.run = function(command, fallback) {
    if (this.isOpen()) {
      this._executeFallback(fallback || function() {});
    }
    else {
      this._executeCommand(command);
    }
  };

  CircuitBreaker.prototype.forceClose = function() {
    this._forced = this._state;
    this._state = CircuitBreaker.CLOSED;
  };

  CircuitBreaker.prototype.forceOpen = function() {
    this._forced = this._state;
    this._state = CircuitBreaker.OPEN;
  };

  CircuitBreaker.prototype.unforce = function() {
    this._state = this._forced;
    this._forced = false;
  };

  CircuitBreaker.prototype.isOpen = function() {
    return this._state == CircuitBreaker.OPEN;
  };

  // PRIVATE

  CircuitBreaker.prototype._startTicker = function() {
    var self = this;
    var bucketIndex = 0;
    var bucketDuration = this.windowDuration / this.numBuckets;

    var tick = function() {
      if (self._buckets.length > self.numBuckets) {
        self._buckets.shift();
      }

      bucketIndex++;

      if (bucketIndex > self.numBuckets) {
        bucketIndex = 0;

        if (self.isOpen()) {
          self._state = CircuitBreaker.HALF_OPEN;
        }
      }

      self._buckets.push(self._createBucket());
    };

    setInterval(tick, bucketDuration);
  };

  CircuitBreaker.prototype._createBucket = function() {
    return { failures: 0, successes: 0, timeouts: 0, shortCircuits: 0 };
  };

  CircuitBreaker.prototype._lastBucket = function() {
    return this._buckets[this._buckets.length - 1];
  };

  CircuitBreaker.prototype._executeCommand = function(command) {
    var self = this;
    var timeout;

    var increment = function(prop) {
      return function() {
        if (!timeout) { return; }

        var bucket = self._lastBucket();
        bucket[prop]++;

        if (!self._forced) {
          self._updateState();
        }

        clearTimeout(timeout);
        timeout = null;
      }
    };

    timeout = setTimeout(increment('timeouts'), this.timeoutDuration);

    command(increment('successes'), increment('failures'));
  };

  CircuitBreaker.prototype._executeFallback = function(fallback) {
    fallback();

    var bucket = this._lastBucket();
    bucket.shortCircuits++;
  };

  CircuitBreaker.prototype._calculateMetrics = function() {
    var totalCount = 0, errorCount = 0, errorPercentage = 0;

    for (var i = 0, l = this._buckets.length; i < l; i++) {
      var bucket = this._buckets[i];
      var errors = (bucket.failures + bucket.timeouts);

      errorCount += errors;
      totalCount += (errors + bucket.successes);
    }

    errorPercentage = (errorCount / (totalCount > 0 ? totalCount : 1)) * 100;

    return { totalCount: totalCount, errorCount: errorCount, errorPercentage: errorPercentage };
  }

  CircuitBreaker.prototype._updateState = function() {
    var metrics = this._calculateMetrics();

    if (this._state == CircuitBreaker.HALF_OPEN) {
      var lastCommandFailed = !this._lastBucket().successes && metrics.errorCount > 0;

      if (lastCommandFailed) {
        this._state = CircuitBreaker.OPEN;
      }
      else {
        this._state = CircuitBreaker.CLOSED;
        this.onCircuitClose();
      }
    }
    else {
      var overErrorThreshold = metrics.errorPercentage > this.errorThreshold;
      var overVolumeThreshold = metrics.totalCount > this.volumeThreshold;
      var overThreshold = overVolumeThreshold && overErrorThreshold;

      if (overThreshold) {
        this._state = CircuitBreaker.OPEN;
        this.onCircuitOpen();
      }
    }
  };

  var assign = function(name, obj) {
    var commonJS = module && module.exports;

    if (commonJS) {
      module.exports = obj;
    }
    else {
      window[name] = obj;
    }
  };

  assign('CircuitBreaker', CircuitBreaker);
})();
<|endoftext|>


<commit_before>/**
 * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or http://ckeditor.com/license
 */

CKEDITOR.editorConfig = function (config) {
    config.allowedContent = true;
    config.language = 'zh-cn';
    //config.skin = 'minimalist';
    config.pasteFilter = null;
    config.forcePasteAsPlainText = false;
    config.allowedContent = true;
    config.pasteFromWordRemoveFontStyles = false;
    config.pasteFromWordRemoveStyles = false;
    config.extraPlugins = 'floating-tools,notification,autosave,templates,markdown,wordcount,clipboard';
    config.height = 637;

    config.wordcount = {

        // Whether or not you want to show the Paragraphs Count
        showParagraphs: true,

        // Whether or not you want to show the Word Count
        showWordCount: true,

        // Whether or not you want to show the Char Count
        showCharCount: true,

        // Whether or not you want to count Spaces as Chars
        countSpacesAsChars: true,

        // Whether or not to include Html chars in the Char Count
        countHTML: true
    };

  config.toolbar = [
        {name: 'document', items: ['Preview', 'Templates']},
        {name: 'clipboard', items: ['Undo', 'Redo']},
        {
            name: 'basicstyles',
            items: ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat']
        },
        {
            name: 'paragraph',
            items: ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv',
                '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']
        },
        '/',
        {name: 'styles', items: ['Format', 'Font', 'FontSize']},
        {name: 'colors', items: ['TextColor', 'BGColor']},
        {name: 'tools', items: ['Maximize', 'ShowBlocks', 'Source', 'Markdown']}
    ];
};
<commit_msg>add pastefromword<commit_after>/**
 * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or http://ckeditor.com/license
 */

CKEDITOR.editorConfig = function (config) {
    config.allowedContent = true;
    config.language = 'zh-cn';
    //config.skin = 'minimalist';
    config.pasteFilter = null;
    config.forcePasteAsPlainText = false;
    config.allowedContent = true;
    config.pasteFromWordRemoveFontStyles = false;
    config.pasteFromWordRemoveStyles = false;
    config.extraPlugins = 'floating-tools,notification,autosave,templates,markdown,wordcount,clipboard,pastefromword';
    config.height = 637;

    config.wordcount = {

        // Whether or not you want to show the Paragraphs Count
        showParagraphs: true,

        // Whether or not you want to show the Word Count
        showWordCount: true,

        // Whether or not you want to show the Char Count
        showCharCount: true,

        // Whether or not you want to count Spaces as Chars
        countSpacesAsChars: true,

        // Whether or not to include Html chars in the Char Count
        countHTML: true
    };

  config.toolbar = [
        {name: 'document', items: ['Preview', 'Templates']},
        {name: 'clipboard', items: ['Undo', 'Redo']},
        {
            name: 'basicstyles',
            items: ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat']
        },
        {
            name: 'paragraph',
            items: ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv',
                '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']
        },
        '/',
        {name: 'styles', items: ['Format', 'Font', 'FontSize']},
        {name: 'colors', items: ['TextColor', 'BGColor']},
        {name: 'tools', items: ['Maximize', 'ShowBlocks', 'Source', 'Markdown']}
    ];
};
<|endoftext|>


<commit_before><commit_msg>Adding helpful titles for icon names.<commit_after><|endoftext|>


<commit_before><commit_msg>fix js error<commit_after><|endoftext|>


<commit_before><commit_msg>Robots drop loot when killed<commit_after><|endoftext|>


<commit_before><commit_msg>Added editSelf message option<commit_after><|endoftext|>


<commit_before><commit_msg>improve regex for time offset<commit_after><|endoftext|>