<commit_before>/*
 * MediaCrush JavaScript Library
 * https://mediacru.sh
 * https://github.com/MediaCrush/MediaCrush
 * MIT License http://opensource.org/licenses/MIT
 */
window.MediaCrush = (function() {
    var self = this;
    self.version = 1;
    self.domain = 'https://mediacru.sh';
    self.maxMediaWidth = -1;
    self.maxMediaHeight = -1;
    self.preserveAspectRatio = true;

    /*
     * Private methods/properties
     */
    var createRequest = function(method, url) {
        var xhr = new XMLHttpRequest();
        xhr.open(method, self.domain + url);
        xhr.setRequestHeader('X-CORS-Status', 'true');
        return xhr;
    };

    var createMediaObject = function(blob) {
        blob.status = null;
        blob.url = self.domain + '/' + blob.hash;
        blob.update = function(callback) {
            self.checkStatus(blob.hash, function(value, result) {
                if (value == 'done') {
                    for (prop in result[blob.hash]) {
                        blob[prop] = result[blob.hash][prop];
                    }
                }
                blob.status = value;
                if (callback)
                    callback(blob);
            });
        };
        blob.delete = function(callback) {
            self.delete(blob.hash, function(value) {
                if (callback)
                    callback(value);
            });
        };
        blob.wait = function(callback) {
            if (blob.status != 'processing') {
                if (callback)
                    callback(blob);
            } else {
                setTimeout(function() {
                    blob.update(function(a) { a.wait(callback); });
                }, 1000);
            }
        };
        return blob;
    };

    var renderImage = function(target, media, callback) {
        var image = document.createElement('img');
        image.src = self.domain + media.files[0].file;
        if (self.maxMediaWidth != -1) {
            img.style.maxWidth = self.maxMediaWidth;
        }
        if (self.maxMediaHeight != -1) {
            img.style.maxHeight = self.maxMediaHeight;
        }
        target.appendChild(image);
        target.classList.remove('mediacrush');
        target.classList.add('mediacrush-processed');
        if (callback)
            callback(target, media);
    };

    var iframes = {};
    var renderMedia = function(target, media, callback) {
        var iframe = document.createElement('iframe');
        iframe.src = self.domain + '/' + media.hash + '/frame';
        iframe.setAttribute('frameborder', 0);
        iframe.allowFullscreen = true;
        iframes[iframe.src] = { frame: iframe, media: media, callback: callback };
        target.appendChild(iframe);
        target.classList.remove('mediacrush');
        target.classList.add('mediacrush-processed');
    };

    window.addEventListener('message', function(e) {
        var frame = iframes[e.source.location.href];
        if (frame) {
            var width = e.data.width;
            var height = e.data.height;
            if (self.maxMediaWidth != -1) {
                if (width > maxMediaWidth) {
                    var difference = maxMediaWidth / width;
                    width = maxMediaWidth;
                    if (self.preserveAspectRatio) {
                        height = height * difference;
                    }
                }
            }
            if (self.maxMediaHeight != -1) {
                if (height > maxMediaHeight) {
                    var difference = maxMediaHeight / height;
                    height = maxMediaHeight;
                    if (self.preserveAspectRatio) {
                        width = width * difference;
                    }
                }
            }
            frame.frame.width = width;
            frame.frame.height = height;
        }
        if (frame.callback)
            frame.callback(frame.frame.parentElement, frame.media);
    }, false);

    /*
     * Retrieves information for the specified hashes.
     */
    self.get = function(hashes, callback) {
        var xhr;
        if (hashes instanceof Array) {
            xhr = createRequest('GET', '/api/info?list=' + hashes.join(','));
        } else {
            xhr = createRequest('GET', '/api/' + hashes);
        }
        xhr.onload = function() {
            if (callback) {
                var result = JSON.parse(this.responseText);
                if (hashes instanceof Array) {
                    var array = [];
                    var dictionary = {};
                    for (blob in result) {
                        result[blob].hash = blob;
                        if (result[blob] == null) {
                            array.push(result[blob]);
                            dictionary[blob] = result[blob];
                        } else {
                            var media = createMediaObject(result[blob]);
                            array.push(media);
                            dictionary[blob] = media;
                        }
                    }
                    if (callback)
                        callback(array, dictionary);
                } else {
                    if (callback)
                        callback(createMediaObject(result));
                }
            }
        };
        xhr.send();
    };

    /*
     * Checks if the specified hash exists on MediaCrush.
     */
    self.exists = function(hash, callback) {
        var xhr = createRequest('GET', '/api/' + hash + '/exists');
        xhr.onload = function() {
            if (callback)
                callback(JSON.parse(this.responseText).exists);
        };
        xhr.send();
    };

    /*
     * Deletes the specified media blob from MediaCrush, if the user is allowed to.
     */
    self.delete = function(hash, callback) {
        var xhr = createRequest('GET', '/api/' + hash + '/delete');
        xhr.onload = function() {
            var result = JSON.parse(this.responseText);
            if (callback)
                callback(result['x-status'] == 200);
        };
        xhr.onerror = function() {
            if (callback)
                callback(false);
        };
        xhr.send();
    };

    /*
     * Checks the processing status of the specified hash.
     */
    self.checkStatus = function(hash, callback) {
        var xhr = createRequest('GET', '/api/' + hash + '/status');
        xhr.onload = function() {
            var result = JSON.parse(this.responseText);
            if (callback)
                callback(result['x-status'], result);
        };
        xhr.send();
    };

    /*
     * Uploads a file or URL to MediaCrush.
     */
    self.upload = function(file, callback, onprogress) {
        var xhr;
        var formData = new FormData();
        if (file instanceof File) {
            xhr = createRequest('POST', '/api/upload/file');
            formData.append('file', file);
        } else {
            xhr = createRequest('POST', '/api/upload/url');
            formData.append('url', file);
        }
        xhr.onprogress = onprogress;
        xhr.onload = function() {
            var json = JSON.parse(this.responseText);
            var blob = { hash: json.hash };
            if (json['x-status'] == 409) {
                blob.status = 'done';
            } else {
                blob.status = 'processing';
            }
            if (callback)
                callback(createMediaObject(blob));
        };
        xhr.send(formData);
    };

    /*
     * Translates a .mediacrush div into an embedded MediaCrush object.
     */
    self.render = function(element, callback) {
        var hash = element.getAttribute('data-media');
        self.get(hash, function(media) {
            if (media.type.indexOf('image/') == 0 && media.type != 'image/gif') {
                renderImage(element, media, callback);
            } else {
                renderMedia(element, media, callback);
            }
        });
    };

    self.renderAll = function() {
        var elements = document.querySelectorAll('div.mediacrush');
        var hashes = [];
        for (var i = 0; i < elements.length; i++) {
            var hash = elements[i].getAttribute('data-media');
            if (hash) {
                hashes.push(hash);
            }
        }
        if (hashes.length == 0)
            return;
        self.get(hashes, function(array, result) {
            for (var i = 0; i < elements.length; i++) {
                var hash = elements[i].getAttribute('data-media');
                if (hash && result[hash]) {
                    if (result[hash].type.indexOf('image/') == 0 && result[hash].type != 'image/gif') {
                        renderImage(elements[i], result[hash]);
                    } else {
                        renderMedia(elements[i], result[hash]);
                    }
                }
            }
        });
    };

    return self;
}());

window.addEventListener('load', function() {
    if (window.preventMediaCrushAutoload) {
        return;
    }
    if (window.beforeMediaCrushLoad) {
        window.beforeMediaCrushLoad();
    }
    MediaCrush.renderAll();
}, false);
<commit_msg>Hotfix cross-origin problem with mediacrush.js<commit_after>/*
 * MediaCrush JavaScript Library
 * https://mediacru.sh
 * https://github.com/MediaCrush/MediaCrush
 * MIT License http://opensource.org/licenses/MIT
 */
window.MediaCrush = (function() {
    var self = this;
    self.version = 1;
    self.domain = 'https://mediacru.sh';
    self.maxMediaWidth = -1;
    self.maxMediaHeight = -1;
    self.preserveAspectRatio = true;

    /*
     * Private methods/properties
     */
    var createRequest = function(method, url) {
        var xhr = new XMLHttpRequest();
        xhr.open(method, self.domain + url);
        xhr.setRequestHeader('X-CORS-Status', 'true');
        return xhr;
    };

    var createMediaObject = function(blob) {
        blob.status = null;
        blob.url = self.domain + '/' + blob.hash;
        blob.update = function(callback) {
            self.checkStatus(blob.hash, function(value, result) {
                if (value == 'done') {
                    for (prop in result[blob.hash]) {
                        blob[prop] = result[blob.hash][prop];
                    }
                }
                blob.status = value;
                if (callback)
                    callback(blob);
            });
        };
        blob.delete = function(callback) {
            self.delete(blob.hash, function(value) {
                if (callback)
                    callback(value);
            });
        };
        blob.wait = function(callback) {
            if (blob.status != 'processing') {
                if (callback)
                    callback(blob);
            } else {
                setTimeout(function() {
                    blob.update(function(a) { a.wait(callback); });
                }, 1000);
            }
        };
        return blob;
    };

    var renderImage = function(target, media, callback) {
        var image = document.createElement('img');
        image.src = self.domain + media.files[0].file;
        if (self.maxMediaWidth != -1) {
            img.style.maxWidth = self.maxMediaWidth;
        }
        if (self.maxMediaHeight != -1) {
            img.style.maxHeight = self.maxMediaHeight;
        }
        target.appendChild(image);
        target.classList.remove('mediacrush');
        target.classList.add('mediacrush-processed');
        if (callback)
            callback(target, media);
    };

    var iframes = {};
    var renderMedia = function(target, media, callback) {
        var iframe = document.createElement('iframe');
        iframe.setAttribute('sandbox', 'allow-same-origin');
        iframe.src = self.domain + '/' + media.hash + '/frame';
        iframe.setAttribute('frameborder', 0);
        iframe.allowFullscreen = true;
        iframes[iframe.src] = { frame: iframe, media: media, callback: callback };
        target.appendChild(iframe);
        target.classList.remove('mediacrush');
        target.classList.add('mediacrush-processed');
    };

    window.addEventListener('message', function(e) {
        var frame = iframes[e.source.location.href];
        if (frame) {
            var width = e.data.width;
            var height = e.data.height;
            if (self.maxMediaWidth != -1) {
                if (width > maxMediaWidth) {
                    var difference = maxMediaWidth / width;
                    width = maxMediaWidth;
                    if (self.preserveAspectRatio) {
                        height = height * difference;
                    }
                }
            }
            if (self.maxMediaHeight != -1) {
                if (height > maxMediaHeight) {
                    var difference = maxMediaHeight / height;
                    height = maxMediaHeight;
                    if (self.preserveAspectRatio) {
                        width = width * difference;
                    }
                }
            }
            frame.frame.width = width;
            frame.frame.height = height;
        }
        if (frame.callback)
            frame.callback(frame.frame.parentElement, frame.media);
    }, false);

    /*
     * Retrieves information for the specified hashes.
     */
    self.get = function(hashes, callback) {
        var xhr;
        if (hashes instanceof Array) {
            xhr = createRequest('GET', '/api/info?list=' + hashes.join(','));
        } else {
            xhr = createRequest('GET', '/api/' + hashes);
        }
        xhr.onload = function() {
            if (callback) {
                var result = JSON.parse(this.responseText);
                if (hashes instanceof Array) {
                    var array = [];
                    var dictionary = {};
                    for (blob in result) {
                        result[blob].hash = blob;
                        if (result[blob] == null) {
                            array.push(result[blob]);
                            dictionary[blob] = result[blob];
                        } else {
                            var media = createMediaObject(result[blob]);
                            array.push(media);
                            dictionary[blob] = media;
                        }
                    }
                    if (callback)
                        callback(array, dictionary);
                } else {
                    if (callback)
                        callback(createMediaObject(result));
                }
            }
        };
        xhr.send();
    };

    /*
     * Checks if the specified hash exists on MediaCrush.
     */
    self.exists = function(hash, callback) {
        var xhr = createRequest('GET', '/api/' + hash + '/exists');
        xhr.onload = function() {
            if (callback)
                callback(JSON.parse(this.responseText).exists);
        };
        xhr.send();
    };

    /*
     * Deletes the specified media blob from MediaCrush, if the user is allowed to.
     */
    self.delete = function(hash, callback) {
        var xhr = createRequest('GET', '/api/' + hash + '/delete');
        xhr.onload = function() {
            var result = JSON.parse(this.responseText);
            if (callback)
                callback(result['x-status'] == 200);
        };
        xhr.onerror = function() {
            if (callback)
                callback(false);
        };
        xhr.send();
    };

    /*
     * Checks the processing status of the specified hash.
     */
    self.checkStatus = function(hash, callback) {
        var xhr = createRequest('GET', '/api/' + hash + '/status');
        xhr.onload = function() {
            var result = JSON.parse(this.responseText);
            if (callback)
                callback(result['x-status'], result);
        };
        xhr.send();
    };

    /*
     * Uploads a file or URL to MediaCrush.
     */
    self.upload = function(file, callback, onprogress) {
        var xhr;
        var formData = new FormData();
        if (file instanceof File) {
            xhr = createRequest('POST', '/api/upload/file');
            formData.append('file', file);
        } else {
            xhr = createRequest('POST', '/api/upload/url');
            formData.append('url', file);
        }
        xhr.onprogress = onprogress;
        xhr.onload = function() {
            var json = JSON.parse(this.responseText);
            var blob = { hash: json.hash };
            if (json['x-status'] == 409) {
                blob.status = 'done';
            } else {
                blob.status = 'processing';
            }
            if (callback)
                callback(createMediaObject(blob));
        };
        xhr.send(formData);
    };

    /*
     * Translates a .mediacrush div into an embedded MediaCrush object.
     */
    self.render = function(element, callback) {
        var hash = element.getAttribute('data-media');
        self.get(hash, function(media) {
            if (media.type.indexOf('image/') == 0 && media.type != 'image/gif') {
                renderImage(element, media, callback);
            } else {
                renderMedia(element, media, callback);
            }
        });
    };

    self.renderAll = function() {
        var elements = document.querySelectorAll('div.mediacrush');
        var hashes = [];
        for (var i = 0; i < elements.length; i++) {
            var hash = elements[i].getAttribute('data-media');
            if (hash) {
                hashes.push(hash);
            }
        }
        if (hashes.length == 0)
            return;
        self.get(hashes, function(array, result) {
            for (var i = 0; i < elements.length; i++) {
                var hash = elements[i].getAttribute('data-media');
                if (hash && result[hash]) {
                    if (result[hash].type.indexOf('image/') == 0 && result[hash].type != 'image/gif') {
                        renderImage(elements[i], result[hash]);
                    } else {
                        renderMedia(elements[i], result[hash]);
                    }
                }
            }
        });
    };

    return self;
}());

window.addEventListener('load', function() {
    if (window.preventMediaCrushAutoload) {
        return;
    }
    if (window.beforeMediaCrushLoad) {
        window.beforeMediaCrushLoad();
    }
    MediaCrush.renderAll();
}, false);
<|endoftext|>


<commit_before>export { Params, Base };

import * as ValueMax from "../ValueMax.js";
import * as Weights from "../Weights.js";

/**
 * Embedding (2d) layer parameters.
 */
class Params extends Weights.Params {

  /**
   * @param {number} channelMultiplier
   *   Every input channel will be expanded into so many embedding channels. The outChannels (output channel count)
   * is always depending on channelMultiplier and equal to ( inChannels * channelMultiplier ). If null, it will be
   * extracted from inputFloat32Array (i.e. by evolution).
   *
   * @return {boolean} Return false, if initialization failed.
   *
   * @override
   */
  init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier = null ) {

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    let parameterMap = new Map( [
      [ Weights.Params.Keys.inChannels,        inChannels ],
      [ Weights.Params.Keys.channelMultiplier, channelMultiplier ],

      // For an embedding layer, its output channel count always depends on channelMultiplier.
      [ Weights.Params.Keys.outChannels,       Infinity ],
    ] );

    return super.init( inputFloat32Array, byteOffsetBegin, parameterMap );
  }

}

/**
 * Embedding could achieve non-linear mapping (just like any perceptron). But it is achieved by lookup table (instead
 * of weighted sum, bias and activation function). This implies:
 *   - It may use more (CPU or GPU) memory, but may use less (CPU or GPU) computation.
 *   - It can only achieve channel expansion, and can not achieve channel aggregation. (because no weighted sum)
 *   - It can only represent context-independent (not context-dependent) information. (because no weighted sum)
 *   - It can only handle integer input (i.e. int32, not float32).
 *
 * It is useful as the first layer of text or image processing because their inputs are all integer (e.g. character codes,
 * word indices, color codes, etc). And, the first layer only needs carry context-independent information (and all the other
 * layers after it will produce context-dependent information).
 *
 * This object always accepts tensor3d (dtype = int32).
 *   - The axis 0 is height. (Image height) (Text lines and usually only 1 line.)
 *   - The axis 1 is width. (Image width) (Text length (e.g. character count).)
 *   - The axis 2 is channel. (Image color channel) (Text character code channel and usually only 1 channel.)
 *
 * An embedding layer contains one params (this.params) and inChannels embedding vocabulary tables.
 *   - Every input channel has one embedding vocabulary table.
 *   - Every embedding vocabulary table has vocabularyCountPerInputChannel vocabularies.
 *   - Every vocabulary has channelMultiplier embedding channels.
 *
 *
 *
 * @member {number} vocabularyCountPerInputChannel
 *   Every input channel will have how many vocabularies. This is also vocabulary count per vocabulary table (because
 * every input channel has a vocabulary table). For an image data (R-G-B-A four channels), there will be 256
 * vocabularies per input channel because every channel is represented by one byte (8 bits) which has 2^8 = 256 kinds
 * of possible values.
 *
 * @member {number} embeddingChannelCountPerInputChannel
 *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
 * embedding channels. This is the same as the this.params.channelMultiplier.
 */
class Base {

  /**
   * Generator for initializing this object.
   *
   * @param {ValueMax.Percentage.Aggregate} progressParent
   *   Some new progressToAdvance will be created and added to progressParent. The created progressToAdvance will be
   * increased when every time advanced. The progressParent.getRoot() will be returned when every time yield.
   *
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpret as weights.
   *
   * @param {number} byteOffsetBegin
   *   The position to start to decode from the inputFloat32Array. This is relative to the inputFloat32Array.buffer
   * (not to the inputFloat32Array.byteOffset).
   *
   * @param {number} inChannels
   *   The input channel count.
   *
   * @param {number} channelMultiplier
   *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
   * embedding channels. The outChannels (output channel count) is always depending on channelMultiplier and equal
   * to ( inChannels * channelMultiplier ). If null, it will be extracted from inputFloat32Array (i.e. by evolution).
   *
   * @param {boolean} bEmbedVocabularyId
   *   If true, one of embedding channels will be an auto-generated vocabulary id (i.e. 0, 1, 2, ...). So only
   * ( channelMultiplier - 1 ) embedding channels will be extracted from inputFloat32Array. The extra vocabulary id
   * channel achieves residual connection. Residual connection means apply_and_destroy_or_keep() will append (concatenate)
   * input to output. Since apply_and_destroy_or_keep()'s input is just vocabulary id (one channel or multiple channels),
   * pre-embedded vocabulary id inside the embedding table acheives the same effect by less computation (but more memory).
   *
   * @param {boolean} bKeepInputTensor
   *   If true, apply_and_destroy_or_keep() will not dispose inputTensor (i.e. keep). For example, for the branch of step 0 of ShuffleNetV2.
   * For another example, the input image should be shared across many neural networks.
   *
   * @yield {ValueMax.Percentage.Aggregate}
   *   Yield ( value = progressParent.getRoot() ) when ( done = false ).
   *
   * @yield {boolean}
   *   Yield ( value = true ) when ( done = true ) successfully.
   *   Yield ( value = false ) when ( done = true ) failed.
   */
  * initer(
    progressParent,
    inputFloat32Array, byteOffsetBegin,
    inChannels, vocabularyCountPerInputChannel, channelMultiplier = null,
    bKeepInputTensor
  ) {

    // Estimate the maximum value of progress.
    let progressMax =
      1             // for extracting parameters from inputFloat32Array.
      + inChannels  // for extracting vocabulary table of every input channel from inputFloat32Array.
      + inChannels; // for building vocabulary table tensor2d of every input channel.

    let progressRoot = progressParent.getRoot();
    let progressToAdvance = progressParent.addChild( new ValueMax.Percentage.Concrete( progressMax ) );

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    disposeTensors();
    this.params = this.vocabularyTables = null; // So that distinguishable if re-initialization failed.

    this.vocabularyCountPerInputChannel = vocabularyCountPerInputChannel;
    this.bKeepInputTensor = bKeepInputTensor;

    this.params = new Params();
    if ( !this.params.init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier ) )
      return false;

    ++progressToAdvance.value;
    yield progressRoot;  // Parameters extracted. Report progress.

    let embeddingChannelCountPerInputChannel = this.embeddingChannelCountPerInputChannel; // The real channelMultiplier.
    let vocabularyTableShape = [ vocabularyCountPerInputChannel, embeddingChannelCountPerInputChannel ];

    // Extract data of vocabulary tables from inputFloat32Array.
    this.vocabularyTables = new Array( inChannels );
    {
      let nextByteOffsetBegin = this.params.defaultByteOffsetEnd;
      for ( let i = 0; i < inChannels; ++i ) {
        this.vocabularyTables[ i ] = new Weights.Base();
        if ( !this.vocabularyTables[ i ].init( inputFloat32Array, nextByteOffsetBegin, null, 0, vocabularyTableShape ) )
          return false;  // e.g. input array do not have enough data.
        nextByteOffsetBegin = this.vocabularyTables[ i ].defaultByteOffsetEnd;

        ++progressToAdvance.value;
        yield progressRoot;  // One vocabulary table extracted. Report progress.
      }
    }

    // Build tf.tensor of vocabulary tables.
    try {
      // Create vocabulary id list (tensor2d). (for concatenating with vocabulary table)
      let numberSequencer = new Array( vocabularyCountPerInputChannel ).keys(); // Generator: 0, 1, 2, ..., ( vocabularyCountPerInputChannel - 1 )
      const idsTensor2d = tf.tensor2d( [ ...numberSequencer ], [ vocabularyCountPerInputChannel, 1 ] );

      try {
        let theLastAxisId = ( vocabularyTableShape.length - 1 ); // e.g. will be 1 for tensor2d.
        this.vocabularyTablesTensor2dArray = new Array( this.vocabularyTables.length );
        for ( let i = 0; i < this.vocabularyTables.length; ++i ) {

          // Create an embedding vocabulary table (without vocabulary id).
          const vocabularyTableTensor2dWithoutIds = tf.tensor2d( this.vocabularyTables[ i ], vocabularyTableShape );

          try {
            // Concatenate vocabulary id prefix vocabulary table.
            //
            // This is a residual connection for embedding layer. This concatenating uses some GPU memory space.
            // It, however, reduces some calculation time when apply_and_destroy_or_keep() because the residual
            // connection is already created in advance (here).
            //
            // Why is it (the vocabulary id) residual connection? Because the data of every input channel is just vocabulary id, too.
            this.vocabularyTablesTensor2dArray[ i ] = idsTensor2d.concat( vocabularyTableTensor2dWithoutIds, theLastAxisId );
          } catch ( e ) {
            return false; // e.g. out of (GPU) memory.
          } finally {
            vocabularyTableTensor2dWithoutIds.dispose();
          }

          ++progressToAdvance.value;
          yield progressRoot;  // One vocabulary table tensor2d built. Report progress.
        }

      } catch ( e ) {
        return false; // e.g. out of (GPU) memory.
      } finally {
        idsTensor2d.dispose();
      }

    } catch ( e ) {
      return false; // e.g. out of (GPU) memory.
    }

    return true; // Initialized successfully.
  }

  /** @return {boolean} Return true if this object initialized (i.e. initer()) successfully. */
  isValid() {
    if ( this.vocabularyTablesTensor2dArray )
      if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ] ) // At least, there should be one vocabulary table.
        if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ].isValid() )  // the last vocabulary table is valid.
          return true;
    return false;
  }

  /** Release tf.tensor. */
  disposeTensors() {
    if ( this.vocabularyTablesTensor2dArray ) {
      Base.disposeTensorArray_NotNull( this.vocabularyTablesTensor2dArray );
      this.vocabularyTablesTensor2dArray = null;
    }
  }

  /**
   * Process the input and produce output by using the weights of this neural network layer.
   *
   * @param {tf.tensor3d} inputTensor3d
   *   A tensor3d data (e.g. height-width-color for color image, or 1-width-1 for text) with this.inChannels
   * (e.g. 4 for r-g-b-a, or 1 for text) channels. The inputTensor3d.dtype must be int32 (i.e. can not be float32)
   * so that they can be used as tf.gather()'s indices. If ( this.bKeepInputTensor == false ), this inputTensor3d
   * will be disposed. If ( this.bKeepInputTensor == true ), this inputTensor3d will be kept.
   *
   * @return {tf.tensor3d} The predicted output as tensor3d. Return null, if failed (e.g. out of GPU memory).
   */
  apply_and_destroy_or_keep( inputTensor3d ) {

//!!! ...unfinished... could use gahter, gather, concat instead of split, gather, concat?

    // For example, suppose input is a color image (i.e. height-width-color tensor3d). The last
    // axis is a 4 color (r-g-b-a) channel. Splitting along the last axis (the color channel)
    // results in an array [ r, g, b, a ] which has 4 tensor3d (in fact, they should be
    // viewed as tensor1d).
    let theLastAxisId = ( inputTensor3d.shape.length - 1 );  // Or, ( inputTensor3d.rank - 1 )

    // For a 4 color (r-g-b-a) channel image, splitCount will be 4.
    //
    // This should be the same as this.inChannels.
    let splitCount = inputTensor3d.shape[ theLastAxisId ];

    // Extract vocabulary indices from input.
    //
    // Split the last axis (of input) as many as the shape size (of the last axis) (i.e. become tensor2d).
    // In fact, the result is still tensor3d but has only one channel.
    const vocabularyIndicesOneChannelTensor3dArray = inputTensor3d.split( splitCount, theLastAxisId );

    try {
//!!! ...unfinished... could re-use this array shell (without re-allocating every time apply_and_destroy_or_keep() is called)?
      let embeddedTensor3dArray = new Array( vocabularyIndicesOneChannelTensor3dArray.length );

      try {

        // Embedding (looking up different vocabulary tables according to channel index of vocabulary indices).
        // Every tensor3d (one channel) will be expanded to tensor3d (multiple channels).
        //
        // Note: this.vocabularyTablesTensor2dArray[] already be prefixed vocabulary id (when init()). So it
        // has residual connection in advance.
        for ( let channelIndex = 0; channelIndex < vocabularyIndicesOneChannelTensor3dArray.length; ++channelIndex ) {
          let oneChannelTensor3d = vocabularyIndicesOneChannelTensor3dArray[ channelIndex ];
          let embeddedTensor3d = this.vocabularyTablesTensor2dArray[ channelIndex ].gather( oneChannelTensor3d );
          embeddedTensor3dArray[ channelIndex ] = embeddedTensor3d;
        }

        // Concatenate along the last axis, so that it is still tensor3d but with embedded (more) channels in the last axis.
        let predictResult = tf.concat( embeddedTensor3dArray, theLastAxisId );

//!!! ...unfinished... could using function pointer to avoid conditional-branching?
        if ( !this.bKeepInputTensor )
          inputTensor3d.dispose();

        return predictResult;

      } catch ( e ) {
        throw e;

      } finally {
        Base.disposeTensorArray_NotNull( embeddedTensor3dArray ); // Avoid tensors' memory leakage.
        //embeddedTensor3dArray = null;
      }

    } catch ( e ) {
      throw e;

    } finally {
      Base.disposeTensorArray_NotNull( vocabularyIndicesOneChannelTensor3dArray ); // Avoid tensors' memory leakage.
      //vocabularyIndicesOneChannelTensor3dArray = null;
    }

//!!! ...unfinished... squeeze-and-excitation.
  }

  /**
   * Release an array of tf.tensor.
   *
   * This method is a little like tf.dispose() but can only handle one-layer array. But it should be faster than
   * tf.dispose( an_array ) because no dynamic memory allocation.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array contains tf.tensor to be disposed. The tensorArray itself can not be null (for reducing conditional-branch
   * to improve performance). It can not have nested array. Its element must be either tf.tensor or null.
   */
  static disposeTensorArray_NotNull( tensorArray ) {
    for ( let i = 0; i < tensorArray.length; ++i ) {
      let t = tensorArray[ i ];
      if ( t )
        t.dispose();
    }
  }

  get byteOffsetBegin() { return this.params.defaultByteOffsetBegin; }
  get byteOffsetEnd()   { return this.vocabularyTables[ this.params.inChannels - 1 ].defaultByteOffsetEnd; }

  get inChannels()                           { return this.params.inChannels; }

//!!! ...unfinished...  Because residual connection is added, the channel count will be one more.
  get channelMultiplier()                    { return this.params.channelMultiplier; }
  get channelMultiplier_IncludeResidualConnection() { return this.params.channelMultiplier + 1; }
  get embeddingChannelCountPerInputChannel() { return this.params.channelMultiplier ; }
}
<commit_msg>Update Embedding2d.js<commit_after>export { Params, Base };

import * as ValueMax from "../ValueMax.js";
import * as Weights from "../Weights.js";

/**
 * Embedding (2d) layer parameters.
 */
class Params extends Weights.Params {

  /**
   * @param {number} channelMultiplier
   *   Every input channel will be expanded into so many embedding channels. The outChannels (output channel count)
   * is always depending on channelMultiplier and equal to ( inChannels * channelMultiplier ). If null, it will be
   * extracted from inputFloat32Array (i.e. by evolution).
   *
   * @return {boolean} Return false, if initialization failed.
   *
   * @override
   */
  init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier = null ) {

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    let parameterMap = new Map( [
      [ Weights.Params.Keys.inChannels,        inChannels ],
      [ Weights.Params.Keys.channelMultiplier, channelMultiplier ],

      // For an embedding layer, its output channel count always depends on channelMultiplier.
      [ Weights.Params.Keys.outChannels,       Infinity ],
    ] );

    return super.init( inputFloat32Array, byteOffsetBegin, parameterMap );
  }

}

/**
 * Embedding could achieve non-linear mapping (just like any perceptron). But it is achieved by lookup table (instead
 * of weighted sum, bias and activation function). This implies:
 *   - It may use more (CPU or GPU) memory, but may use less (CPU or GPU) computation.
 *   - It can only achieve channel expansion, and can not achieve channel aggregation. (because no weighted sum)
 *   - It can only represent context-independent (not context-dependent) information. (because no weighted sum)
 *   - It can only handle integer input (i.e. int32, not float32).
 *
 * It is useful as the first layer of text or image processing because their inputs are all integer (e.g. character codes,
 * word indices, color codes, etc). And, the first layer only needs carry context-independent information (and all the other
 * layers after it will produce context-dependent information).
 *
 * This object always accepts tensor3d (dtype = int32).
 *   - The axis 0 is height. (Image height) (Text lines and usually only 1 line.)
 *   - The axis 1 is width. (Image width) (Text length (e.g. character count).)
 *   - The axis 2 is channel. (Image color channel) (Text character code channel and usually only 1 channel.)
 *
 * An embedding layer contains one params (this.params) and inChannels embedding vocabulary tables.
 *   - Every input channel has one embedding vocabulary table.
 *   - Every embedding vocabulary table has vocabularyCountPerInputChannel vocabularies.
 *   - Every vocabulary has channelMultiplier embedding channels.
 *
 *
 *
 * @member {boolean} bEmbedVocabularyId
 *   If true, one of embedding channels will be an auto-generated vocabulary id (i.e. 0, 1, 2, ...). So only
 * ( channelMultiplier - 1 ) embedding channels will be extracted from inputFloat32Array. The extra vocabulary id
 * channel achieves residual connection. Residual connection means apply_and_destroy_or_keep() will append (concatenate)
 * input to output. Since apply_and_destroy_or_keep()'s input is just vocabulary id (one channel or multiple channels),
 * pre-embedded vocabulary id inside the embedding table acheives the same effect by less computation (but more memory).
 *
 * @member {number} vocabularyCountPerInputChannel
 *   Every input channel will have how many vocabularies. This is also vocabulary count per vocabulary table (because
 * every input channel has a vocabulary table). For an image data (R-G-B-A four channels), there will be 256
 * vocabularies per input channel because every channel is represented by one byte (8 bits) which has 2^8 = 256 kinds
 * of possible values.
 *
 * @member {number} embeddingChannelCountPerInputChannel
 *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
 * embedding channels. This is the same as the this.params.channelMultiplier.
 */
class Base {

  /**
   * Generator for initializing this object.
   *
   * @param {ValueMax.Percentage.Aggregate} progressParent
   *   Some new progressToAdvance will be created and added to progressParent. The created progressToAdvance will be
   * increased when every time advanced. The progressParent.getRoot() will be returned when every time yield.
   *
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpret as weights.
   *
   * @param {number} byteOffsetBegin
   *   The position to start to decode from the inputFloat32Array. This is relative to the inputFloat32Array.buffer
   * (not to the inputFloat32Array.byteOffset).
   *
   * @param {number} inChannels
   *   The input channel count.
   *
   * @param {number} channelMultiplier
   *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
   * embedding channels. The outChannels (output channel count) is always depending on channelMultiplier and equal
   * to ( inChannels * channelMultiplier ). If null, it will be extracted from inputFloat32Array (i.e. by evolution).
   *
   * @param {boolean} bKeepInputTensor
   *   If true, apply_and_destroy_or_keep() will not dispose inputTensor (i.e. keep). For example, for the branch of step 0 of ShuffleNetV2.
   * For another example, the input image should be shared across many neural networks.
   *
   * @yield {ValueMax.Percentage.Aggregate}
   *   Yield ( value = progressParent.getRoot() ) when ( done = false ).
   *
   * @yield {boolean}
   *   Yield ( value = true ) when ( done = true ) successfully.
   *   Yield ( value = false ) when ( done = true ) failed.
   */
  * initer(
    progressParent,
    inputFloat32Array, byteOffsetBegin,
    inChannels, vocabularyCountPerInputChannel = 256, channelMultiplier = null,
    bEmbedVocabularyId = true,
    bKeepInputTensor
  ) {

    // Estimate the maximum value of progress.
    let progressMax =
      1             // for extracting parameters from inputFloat32Array.
      + inChannels  // for extracting vocabulary table of every input channel from inputFloat32Array.
      + inChannels; // for building vocabulary table tensor2d of every input channel.

    let progressRoot = progressParent.getRoot();
    let progressToAdvance = progressParent.addChild( new ValueMax.Percentage.Concrete( progressMax ) );

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    disposeTensors();
    this.params = this.vocabularyTables = null; // So that distinguishable if re-initialization failed.

    this.vocabularyCountPerInputChannel = vocabularyCountPerInputChannel;
    this.bEmbedVocabularyId = bEmbedVocabularyId;
    this.bKeepInputTensor = bKeepInputTensor;

    this.params = new Params();
    if ( !this.params.init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier ) )
      return false;

    ++progressToAdvance.value;
    yield progressRoot;  // Parameters extracted. Report progress.

    let embeddingChannelCountPerInputChannel = this.embeddingChannelCountPerInputChannel; // The real channelMultiplier.
    if ( embeddingChannelCountPerInputChannel < 1 )
      return false; // At least, there should be one embedding channel.

    //let vocabularyTableShape = [ vocabularyCountPerInputChannel, embeddingChannelCountPerInputChannel ];
    let vocabularyTableShape_toExtract;
    if ( bEmbedVocabularyId ) {
      // If there will be an auto-generated vocabulary id embedding channel, extract one less channels from data.
      vocabularyTableShape_toExtract = [ vocabularyCountPerInputChannel, embeddingChannelCountPerInputChannel - 1 ];
    } else {
      // Otherwise, all embedding channels are extracted from data.
      vocabularyTableShape_toExtract = [ vocabularyCountPerInputChannel, embeddingChannelCountPerInputChannel ];
    }

    // Extract data of vocabulary tables from inputFloat32Array.
    this.vocabularyTables = new Array( inChannels );
    {
      let nextByteOffsetBegin = this.params.defaultByteOffsetEnd;
      for ( let i = 0; i < inChannels; ++i ) {
        this.vocabularyTables[ i ] = new Weights.Base();
        if ( !this.vocabularyTables[ i ].init( inputFloat32Array, nextByteOffsetBegin, null, 0, vocabularyTableShape_toExtract ) )
          return false;  // e.g. input array do not have enough data.
        nextByteOffsetBegin = this.vocabularyTables[ i ].defaultByteOffsetEnd;

        ++progressToAdvance.value;
        yield progressRoot;  // One vocabulary table extracted. Report progress.
      }
    }

    // Build tf.tensor of vocabulary tables.
    try {
      // Create vocabulary id list (tensor2d). (for concatenating with vocabulary table)
      let numberSequencer = new Array( vocabularyCountPerInputChannel ).keys(); // Generator: 0, 1, 2, ..., ( vocabularyCountPerInputChannel - 1 )
      const idsTensor2d = tf.tensor2d( [ ...numberSequencer ], [ vocabularyCountPerInputChannel, 1 ] );

      try {
        let theLastAxisId = ( vocabularyTableShape_toExtract.length - 1 ); // e.g. will be 1 for tensor2d.
        this.vocabularyTablesTensor2dArray = new Array( this.vocabularyTables.length );
        for ( let i = 0; i < this.vocabularyTables.length; ++i ) {

          // Create an embedding vocabulary table (without vocabulary id).
          const vocabularyTableTensor2dWithoutIds = tf.tensor2d( this.vocabularyTables[ i ], vocabularyTableShape_toExtract );

          if ( bEmbedVocabularyId ) {
            try { // Concatenate vocabulary id prefix vocabulary table (as residual connection).
              this.vocabularyTablesTensor2dArray[ i ] = idsTensor2d.concat( vocabularyTableTensor2dWithoutIds, theLastAxisId );
            } catch ( e ) {
              return false; // e.g. out of (GPU) memory.
            } finally {
              vocabularyTableTensor2dWithoutIds.dispose();
            }
          } else { // No vocabulary id.
            this.vocabularyTablesTensor2dArray[ i ] = vocabularyTableTensor2dWithoutIds;
          }

          ++progressToAdvance.value;
          yield progressRoot;  // One vocabulary table tensor2d built. Report progress.
        }

      } catch ( e ) {
        return false; // e.g. out of (GPU) memory.
      } finally {
        idsTensor2d.dispose();
      }

    } catch ( e ) {
      return false; // e.g. out of (GPU) memory.
    }

    return true; // Initialized successfully.
  }

  /** @return {boolean} Return true if this object initialized (i.e. initer()) successfully. */
  isValid() {
    if ( this.vocabularyTablesTensor2dArray )
      if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ] ) // At least, there should be one vocabulary table.
        if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ].isValid() )  // the last vocabulary table is valid.
          return true;
    return false;
  }

  /** Release tf.tensor. */
  disposeTensors() {
    if ( this.vocabularyTablesTensor2dArray ) {
      Base.disposeTensorArray_NotNull( this.vocabularyTablesTensor2dArray );
      this.vocabularyTablesTensor2dArray = null;
    }
  }

  /**
   * Process the input and produce output by using the weights of this neural network layer.
   *
   * @param {tf.tensor3d} inputTensor3d
   *   A tensor3d data (e.g. height-width-color for color image, or 1-width-1 for text) with this.inChannels
   * (e.g. 4 for r-g-b-a, or 1 for text) channels. The inputTensor3d.dtype must be int32 (i.e. can not be float32)
   * so that they can be used as tf.gather()'s indices. If ( this.bKeepInputTensor == false ), this inputTensor3d
   * will be disposed. If ( this.bKeepInputTensor == true ), this inputTensor3d will be kept.
   *
   * @return {tf.tensor3d} The predicted output as tensor3d. Return null, if failed (e.g. out of GPU memory).
   */
  apply_and_destroy_or_keep( inputTensor3d ) {

//!!! ...unfinished... could use gahter, gather, concat instead of split, gather, concat?

    // For example, suppose input is a color image (i.e. height-width-color tensor3d). The last
    // axis is a 4 color (r-g-b-a) channel. Splitting along the last axis (the color channel)
    // results in an array [ r, g, b, a ] which has 4 tensor3d (in fact, they should be
    // viewed as tensor1d).
    let theLastAxisId = ( inputTensor3d.shape.length - 1 );  // Or, ( inputTensor3d.rank - 1 )

    // For a 4 color (r-g-b-a) channel image, splitCount will be 4.
    //
    // This should be the same as this.inChannels.
    let splitCount = inputTensor3d.shape[ theLastAxisId ];

    // Extract vocabulary indices from input.
    //
    // Split the last axis (of input) as many as the shape size (of the last axis) (i.e. become tensor2d).
    // In fact, the result is still tensor3d but has only one channel.
    const vocabularyIndicesOneChannelTensor3dArray = inputTensor3d.split( splitCount, theLastAxisId );

    try {
//!!! ...unfinished... could re-use this array shell (without re-allocating every time apply_and_destroy_or_keep() is called)?
      let embeddedTensor3dArray = new Array( vocabularyIndicesOneChannelTensor3dArray.length );

      try {

        // Embedding (looking up different vocabulary tables according to channel index of vocabulary indices).
        // Every tensor3d (one channel) will be expanded to tensor3d (multiple channels).
        //
        // Note: this.vocabularyTablesTensor2dArray[] already be prefixed vocabulary id (when init()). So it
        // has residual connection in advance.
        for ( let channelIndex = 0; channelIndex < vocabularyIndicesOneChannelTensor3dArray.length; ++channelIndex ) {
          let oneChannelTensor3d = vocabularyIndicesOneChannelTensor3dArray[ channelIndex ];
          let embeddedTensor3d = this.vocabularyTablesTensor2dArray[ channelIndex ].gather( oneChannelTensor3d );
          embeddedTensor3dArray[ channelIndex ] = embeddedTensor3d;
        }

        // Concatenate along the last axis, so that it is still tensor3d but with embedded (more) channels in the last axis.
        let predictResult = tf.concat( embeddedTensor3dArray, theLastAxisId );

//!!! ...unfinished... could using function pointer to avoid conditional-branching?
        if ( !this.bKeepInputTensor )
          inputTensor3d.dispose();

        return predictResult;

      } catch ( e ) {
        throw e;

      } finally {
        Base.disposeTensorArray_NotNull( embeddedTensor3dArray ); // Avoid tensors' memory leakage.
        //embeddedTensor3dArray = null;
      }

    } catch ( e ) {
      throw e;

    } finally {
      Base.disposeTensorArray_NotNull( vocabularyIndicesOneChannelTensor3dArray ); // Avoid tensors' memory leakage.
      //vocabularyIndicesOneChannelTensor3dArray = null;
    }

//!!! ...unfinished... squeeze-and-excitation.
  }

  /**
   * Release an array of tf.tensor.
   *
   * This method is a little like tf.dispose() but can only handle one-layer array. But it should be faster than
   * tf.dispose( an_array ) because no dynamic memory allocation.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array contains tf.tensor to be disposed. The tensorArray itself can not be null (for reducing conditional-branch
   * to improve performance). It can not have nested array. Its element must be either tf.tensor or null.
   */
  static disposeTensorArray_NotNull( tensorArray ) {
    for ( let i = 0; i < tensorArray.length; ++i ) {
      let t = tensorArray[ i ];
      if ( t )
        t.dispose();
    }
  }

  get byteOffsetBegin() { return this.params.defaultByteOffsetBegin; }
  get byteOffsetEnd()   { return this.vocabularyTables[ this.params.inChannels - 1 ].defaultByteOffsetEnd; }

  get inChannels()                           { return this.params.inChannels; }

//!!! ...unfinished...  Because residual connection is added, the channel count will be one more.
  get channelMultiplier()                    { return this.params.channelMultiplier; }
  get channelMultiplier_IncludeResidualConnection() { return this.params.channelMultiplier + 1; }
  get embeddingChannelCountPerInputChannel() { return this.params.channelMultiplier ; }
}
<|endoftext|>


<commit_before>'use strict';

(function(module) {

  function MyProject (proj) {
    for (let keys in proj) {
      this[keys] = proj[keys]
    }
  }

  MyProject.allProjects = [];

  MyProject.prototype.toHtml = function() {
    var htmlTemplate = $('#template').html();
    var template = Handlebars.compile(htmlTemplate);
    return template(this);
  };

  MyProject.loadAll = function(project) {
    project.map(function(ele) {
      MyProject.allProjects.push(new MyProject(ele))
    });
  }

  MyProject.getAll = function() {
    if (localStorage.projects) {
      var lStorageMyProjects = JSON.parse(localStorage.getItem('projects'));
      MyProject.loadAll(lStorageMyProjects)
      MyProject.showProjects(lStorageMyProjects)
    } else {
      $.getJSON('./data/projects.json').then(
        function(data) {
          var lData = localStorage.setItem('projects', JSON.stringify(data))
          MyProject.loadAll(lData);
          MyProject.showProjects(lData)
        }
      );
    }
  };

  MyProject.displayTheGroupProjects = function() {
    return MyProject.allProjects.map(function(projects2) {
      return projects2
    })
    .reduce(function(acc, curr) {
      if (acc.indexOf(curr) === -1) {
        acc.push(curr)
      }
      return acc;
    }, []);
  }
  MyProject.getAll();
  //MyProject.displayTheGroupProjects();
  module.MyProject = MyProject;
})(window);
<commit_msg>trying to fix app.js getAll function<commit_after>'use strict';

(function(module) {

  function MyProject (proj) {
    for (let keys in proj) {
      this[keys] = proj[keys]
    }
  }

  MyProject.allProjects = [];

  MyProject.prototype.toHtml = function() {
    var htmlTemplate = $('#template').html();
    var template = Handlebars.compile(htmlTemplate);
    return template(this);
  };

  MyProject.loadAll = function(project) {
    project.forEach(function(ele) {
      MyProject.allProjects.push(new MyProject(ele))
    });
  }

  MyProject.getAll = function() {
    if (localStorage.projects) {
      var lStorageMyProjects = JSON.parse(localStorage.getItem('projects'));
      MyProject.loadAll(lStorageMyProjects)
      MyProject.showProjects(lStorageMyProjects)
    } else {
      $.getJSON('./data/projects.json').then(
        function(data) {
          localStorage.setItem('projects', JSON.stringify(data))
          var lData = JSON.parse(localStorage.getItem('projects'));
          MyProject.loadAll(lData);
          MyProject.showProjects(lData)
        }
      );
    }
  };

  MyProject.displayTheGroupProjects = function() {
    return MyProject.allProjects.map(function(projects2) {
      return projects2
    })
    .reduce(function(acc, curr) {
      if (acc.indexOf(curr) === -1) {
        acc.push(curr)
      }
      return acc;
    }, []);
  }
  MyProject.getAll();
  //MyProject.displayTheGroupProjects();
  module.MyProject = MyProject;
})(window);
<|endoftext|>


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

export { Params, Layer };

/**
 * Embedding (2d) layer parameters.
 */
class Params extends Weights.Params {

  /**
   * @param {number} channelMultiplier
   *   Every input channel will be expanded into so many embedding channels. The outChannels (output channel count)
   * is always depending on channelMultiplier and equal to ( inChannels * channelMultiplier ). If null, it will be
   * extracted from inputFloat32Array (i.e. by evolution).
   *
   * @return {boolean} Return false, if initialization failed.
   *
   * @override
   */
  init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier = null ) {

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    let parameterMap = new Map( [
      [ Weights.Params.Keys.inChannels,        inChannels ],
      [ Weights.Params.Keys.channelMultiplier, channelMultiplier ],

      // For an embedding layer, its output channel count always depends on channelMultiplier.
      [ Weights.Params.Keys.outChannels,       Infinity ],
    ] );

    return super.init( inputFloat32Array, byteOffsetBegin, parameterMap );
  }

}

/**
 * An embedding layer contains one params (this.params) and inChannels embedding vocabulary tables.
 * Every input channel has one embedding vocabulary table. Every embedding vocabulary table has
 * vocabularyCountPerInputChannel vocabularies. Every vocabulary has channelMultiplier embedding channels.
 *
 * @member {Array of number} inputScaleToSize
 *   Scale the height and width of the input image to size [ inputScaleToHeight, inputScaleToWidth ]
 * (in pixels) before convoluting. For text input, the inputScaleToHeight should be 1. If null, there will be
 * no scaling when predict().
 *
 * @member {number} vocabularyCountPerInputChannel
 *   Every input channel will have how many vocabularies. This is also vocabulary count per vocabulary table (because
 * every input channel has a vocabulary table). For an image data (R-G-B-A four channels), there will be 256
 * vocabularies per input channel because every channel is represented by one byte (8 bits) which has 2^8 = 256 kinds
 * of possible values.
 *
 * @member {number} embeddingChannelCountPerInputChannel
 *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
 * embedding channels. This is the same as the this.params.channelMultiplier.
 */
class Layer {

  /**
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpret as weights.
   *
   * @param {number} byteOffsetBegin
   *   The position to start to decode from the inputFloat32Array. This is relative to the inputFloat32Array.buffer
   * (not to the inputFloat32Array.byteOffset).
   *
   * @param {number} inChannels
   *   The input channel count.
   *
   * @param {number} channelMultiplier
   *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
   * embedding channels. The outChannels (output channel count) is always depending on channelMultiplier and equal
   * to ( inChannels * channelMultiplier ). If null, it will be extracted from inputFloat32Array (i.e. by evolution).
   *
   * @return {boolean} Return false, if initialization failed.
   */
  init(
    inputFloat32Array, byteOffsetBegin,
    inputScaleToSize, inChannels, vocabularyCountPerInputChannel, channelMultiplier = null ) {

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    disposeTensors();
    this.params = this.vocabularyTables = null; // So that distinguishable if re-initialization failed.

    this.inputScaleToSize = inputScaleToSize;
    this.vocabularyCountPerInputChannel = vocabularyCountPerInputChannel;

    this.params = new Params();
    if ( !this.params.init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier ) )
      return false;

    let embeddingChannelCountPerInputChannel = this.embeddingChannelCountPerInputChannel; // The real channelMultiplier.
    let vocabularyTableShape = [ vocabularyCountPerInputChannel, embeddingChannelCountPerInputChannel ];

    // Extract data of vocabulary tables.
    this.vocabularyTables = new Array( inChannels );
    {
      let nextByteOffsetBegin = this.params.defaultByteOffsetEnd;
      for ( let i = 0; i < inChannels; ++i ) {
        this.vocabularyTables[ i ] = new Weights.Base();
        if ( !this.vocabularyTables[ i ].init( inputFloat32Array, nextByteOffsetBegin, null, 0, vocabularyTableShape ) )
          return false;  // e.g. input array do not have enough data.
        nextByteOffsetBegin = this.vocabularyTables[ i ].defaultByteOffsetEnd;
      }
    }

    // Build tf.tensor of vocabulary tables.
    try {
      this.vocabularyTablesTensor2dArray = tf.tidy( "Embedding2d.Layer.init.vocabularyTablesTensor2dArray", () => {

        let theLastAxisId = ( vocabularyTableShape.length - 1 ); // e.g. will be 1 for tensor2d.

//!!! ...unfinished... (2020/12/18 Remarked) may create tensor2d directly without expandDims()
//         // Create vocabulary id list. (tensor1d)
//         const vocabularyIdsTensor1d
//           = tf.linspace( 0, ( vocabularyCountPerInputChannel - 1 ), vocabularyCountPerInputChannel );
//
//         // Convert vocabulary id list to tensor2d. (for concatenating with vocabulary table)
//         const vocabularyIdsTensor2d = vocabularyIdsTensor1d.expandDims( theLastAxisId );

        // A generator: 0, 1, 2, ..., ( vocabularyCountPerInputChannel - 1 )
        let zeroBaseNumberSequenceGenerator = Array( vocabularyCountPerInputChannel ).keys();

        // Create vocabulary id list (tensor2d). (for concatenating with vocabulary table)
        const vocabularyIdsTensor2d = tf.tensor2d( [ ...zeroBaseNumberSequenceGenerator ], [ vocabularyCountPerInputChannel, 1 ] );

        return this.vocabularyTables.map( ( vocabularyTable, i ) => {
          return tf.tidy( "Embedding2d.Layer.init.vocabularyTableWithId", () => {

            // Create an embedding vocabulary table (without vocabulary id).
            const vocabularyTable = tf.tensor2d( vocabularyTable, vocabularyTableShape );

            // Concatenate vocabulary id prefix vocabulary table.
            //
            // This is a residual connection for embedding layer. This concatenating uses some GPU memory space.
            // It, however, reduces some calculation time when predict() because the residual connection is already
            // created in advance (here).
            const vocabularyTableWithId = vocabularyIdsTensor2d.concat( vocabularyTable, theLastAxisId );
            return vocabularyTableWithId;
          });
        });
      });
    } catch ( e ) {
      return false; // e.g. out of (GPU) memory.
    }

    return true;
  }

  isValid() {
    if ( this.vocabularyTablesTensor2dArray )
      if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ] ) // At least, there should be one vocabulary table.
        if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ].isValid() )  // the last vocabulary table is valid.
          return true;
    return false;
  }

  /** Release tf.tensor. */
  disposeTensors() {
    if ( this.vocabularyTablesTensor2dArray ) {
      tf.dispose( this.vocabularyTablesTensor2dArray );
      this.vocabularyTablesTensor2dArray = null;
    }
  }

  /**
   * Process the input and produce output by using the weights of this neural network layer.
   *
   * @param {tf.tensor3d} inputTensor3d
   *   A tensor3d data (e.g. height-width-color for color image, or 1-width-1 for text) with
   * this.inChannels (e.g. 4 for r-g-b-a, or 1 for text) channels.
   *
   * @return {tf.tensor3d} The predicted output as tensor3d. Return null, if failed (e.g. out of GPU memory).
   */
  predict( inputTensor3d ) {
    const predictResult = tf.tidy( "Embedding2d.Layer.predict", () => {
      try {

//!!! ...unfinished... dtype int32 resizeBilinear() result which dtype?

        // Scale input into specific size.
        let scaledInput;
        if (   ( !this.inputScaleToSize )                                       // No scaling information.
            || (   ( this.inputScaleToSize[ 0 ] == inputTensor3d.shape[ 0 ] )   // Or, already same size.
                && ( this.inputScaleToSize[ 1 ] == inputTensor3d.shape[ 1 ] ) )
           ) {
          scaledInput = inputTensor3d;
        } else {
          // Otherwise, scaling by using ( alignCorners == true ) for better looking when visualizing.
          scaledInput = inputTensor3d.resizeBilinear( inputTensor3d, this.inputScaleToSize, true );
        }

        // For example, suppose input is a color image (i.e. height-width-color tensor3d). The last
        // axis is a 4 color (r-g-b-a) channel. Splitting along the last axis (the color channel)
        // results in an array [ r, g, b, a ] which has 4 tensor3d (in fact, they should be
        // viewed as tensor1d).
        let theLastAxisId = ( scaledInput.shape.length - 1 );  // Or, ( scaledInput.rank - 1 )

        // For a 4 color (r-g-b-a) channel image, splitCount will be 4.
        //
        // This should be the same as this.inChannels.
        let splitCount = scaledInput.shape[ theLastAxisId ];

        // Extract vocabulary indices from input. (In fact, the result is still tensor3d but has only one channel.)
        const vocabularyIndicesOneChannelTensor3dArray = tf.tidy( "VocabularyIndicesArray", () => {

          // Split the last axis (of input) as many as the shape size (of the last axis) (i.e. become tensor2d).
          // And then convert to integer, so that they can be used as tf.gather()'s indices.
          return scaledInput.split( splitCount, theLastAxisId ).map( t => t.cast('int32') );
        });

        // Embedding (looking up different vocabulary tables according to channel index of vocabulary indices).
        // Every tensor3d (one channel) will be expanded to tensor3d (multiple channels).
        //
        // Note: this.vocabularyTablesTensor2dArray[] already be prefixed vocabulary id (when init()). So it
        // has residual connection in advance.
        const embeddedTensor3dArray = vocabularyIndicesOneChannelTensor3dArray.map(
          ( oneChannelTensor3d, channelIndex ) => {
            return this.vocabularyTablesTensor2dArray[ channelIndex ].gather( oneChannelTensor3d );
        });

//!!! (2020/05/16 Remarked) ...Old... Already residual connection when init().
//         // Embedding (looking up different vocabulary tables according to channel index of vocabulary indices).
//         let embeddedTensor3dArray = [];
//         for ( let i = 0; i < splitCount; ++i ) {
//           // Include the original input channel as residual connection.
//           let oneChannelTensor3d = vocabularyIndicesOneChannelTensor3dArray[ i ];
//           embeddedTensor3dArray.push( oneChannelTensor3d );
//
//           // Every tensor2d (i.e. one channel) will be expanded to tensor3d (i.e. multiple channels).
//           const multipleChannelTensor3d = this.vocabularyTablesTensor2dArray[ i ].gather( oneChannelTensor3d );
//           embeddedTensor3dArray.push( multipleChannelTensor3d );
//         }

//!!! ...unfinished... squeeze-and-excitation.

        // Concatenate along the last axis, so that it is still tensor3d but with embedded (more) channels in the last axis.
        return tf.concat( embeddedTensor3dArray, theLastAxisId );

      } catch ( e ) {
      }
    });

    return predictResult;
  }

  get byteOffsetBegin() { return this.params.defaultByteOffsetBegin; }
  get byteOffsetEnd()   { return this.vocabularyTables[ this.params.inChannels - 1 ].defaultByteOffsetEnd; }

  get inChannels()                           { return this.params.inChannels; }
  get embeddingChannelCountPerInputChannel() { return this.params.channelMultiplier; }
}
<commit_msg>Update Embedding2d.js<commit_after>import * as Weights from "../Weights.js";

export { Params, Layer };

/**
 * Embedding (2d) layer parameters.
 */
class Params extends Weights.Params {

  /**
   * @param {number} channelMultiplier
   *   Every input channel will be expanded into so many embedding channels. The outChannels (output channel count)
   * is always depending on channelMultiplier and equal to ( inChannels * channelMultiplier ). If null, it will be
   * extracted from inputFloat32Array (i.e. by evolution).
   *
   * @return {boolean} Return false, if initialization failed.
   *
   * @override
   */
  init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier = null ) {

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    let parameterMap = new Map( [
      [ Weights.Params.Keys.inChannels,        inChannels ],
      [ Weights.Params.Keys.channelMultiplier, channelMultiplier ],

      // For an embedding layer, its output channel count always depends on channelMultiplier.
      [ Weights.Params.Keys.outChannels,       Infinity ],
    ] );

    return super.init( inputFloat32Array, byteOffsetBegin, parameterMap );
  }

}

/**
 * An embedding layer contains one params (this.params) and inChannels embedding vocabulary tables.
 * Every input channel has one embedding vocabulary table. Every embedding vocabulary table has
 * vocabularyCountPerInputChannel vocabularies. Every vocabulary has channelMultiplier embedding channels.
 *
 * @member {Array of number} inputScaleToSize
 *   Scale the height and width of the input image to size [ inputScaleToHeight, inputScaleToWidth ]
 * (in pixels) before convoluting. For text input, the inputScaleToHeight should be 1. If null, there will be
 * no scaling when predict().
 *
 * @member {number} vocabularyCountPerInputChannel
 *   Every input channel will have how many vocabularies. This is also vocabulary count per vocabulary table (because
 * every input channel has a vocabulary table). For an image data (R-G-B-A four channels), there will be 256
 * vocabularies per input channel because every channel is represented by one byte (8 bits) which has 2^8 = 256 kinds
 * of possible values.
 *
 * @member {number} embeddingChannelCountPerInputChannel
 *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
 * embedding channels. This is the same as the this.params.channelMultiplier.
 */
class Layer {

  /**
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpret as weights.
   *
   * @param {number} byteOffsetBegin
   *   The position to start to decode from the inputFloat32Array. This is relative to the inputFloat32Array.buffer
   * (not to the inputFloat32Array.byteOffset).
   *
   * @param {number} inChannels
   *   The input channel count.
   *
   * @param {number} channelMultiplier
   *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
   * embedding channels. The outChannels (output channel count) is always depending on channelMultiplier and equal
   * to ( inChannels * channelMultiplier ). If null, it will be extracted from inputFloat32Array (i.e. by evolution).
   *
   * @return {boolean} Return false, if initialization failed.
   */
  init(
    inputFloat32Array, byteOffsetBegin,
    inputScaleToSize, inChannels, vocabularyCountPerInputChannel, channelMultiplier = null ) {

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    disposeTensors();
    this.params = this.vocabularyTables = null; // So that distinguishable if re-initialization failed.

    this.inputScaleToSize = inputScaleToSize;
    this.vocabularyCountPerInputChannel = vocabularyCountPerInputChannel;

    this.params = new Params();
    if ( !this.params.init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier ) )
      return false;

    let embeddingChannelCountPerInputChannel = this.embeddingChannelCountPerInputChannel; // The real channelMultiplier.
    let vocabularyTableShape = [ vocabularyCountPerInputChannel, embeddingChannelCountPerInputChannel ];

    // Extract data of vocabulary tables.
    this.vocabularyTables = new Array( inChannels );
    {
      let nextByteOffsetBegin = this.params.defaultByteOffsetEnd;
      for ( let i = 0; i < inChannels; ++i ) {
        this.vocabularyTables[ i ] = new Weights.Base();
        if ( !this.vocabularyTables[ i ].init( inputFloat32Array, nextByteOffsetBegin, null, 0, vocabularyTableShape ) )
          return false;  // e.g. input array do not have enough data.
        nextByteOffsetBegin = this.vocabularyTables[ i ].defaultByteOffsetEnd;
      }
    }

    // Build tf.tensor of vocabulary tables.
/*!!! (2020/12/18 Remarked for without tidy()
    try {
      this.vocabularyTablesTensor2dArray = tf.tidy( "Embedding2d.Layer.init.vocabularyTablesTensor2dArray", () => {

        let theLastAxisId = ( vocabularyTableShape.length - 1 ); // e.g. will be 1 for tensor2d.

        // A generator: 0, 1, 2, ..., ( vocabularyCountPerInputChannel - 1 )
        let zeroBaseNumberSequenceGenerator = Array( vocabularyCountPerInputChannel ).keys();

        // Create vocabulary id list (tensor2d). (for concatenating with vocabulary table)
        const vocabularyIdsTensor2d = tf.tensor2d( [ ...zeroBaseNumberSequenceGenerator ], [ vocabularyCountPerInputChannel, 1 ] );

        return this.vocabularyTables.map( ( vocabularyTable, i ) => {
          return tf.tidy( "Embedding2d.Layer.init.vocabularyTableWithId", () => {

            // Create an embedding vocabulary table (without vocabulary id).
            const vocabularyTable = tf.tensor2d( vocabularyTable, vocabularyTableShape );

            // Concatenate vocabulary id prefix vocabulary table.
            //
            // This is a residual connection for embedding layer. This concatenating uses some GPU memory space.
            // It, however, reduces some calculation time when predict() because the residual connection is already
            // created in advance (here).
            const vocabularyTableWithId = vocabularyIdsTensor2d.concat( vocabularyTable, theLastAxisId );
            return vocabularyTableWithId;
          });
        });
      });
    } catch ( e ) {
      return false; // e.g. out of (GPU) memory.
    }
*/

    try {
      // Create vocabulary id list (tensor2d). (for concatenating with vocabulary table)
      let numberSequencer = new Array( vocabularyCountPerInputChannel ).keys(); // A generator: 0, 1, 2, ..., ( vocabularyCountPerInputChannel - 1 )
      const idsTensor2d = tf.tensor2d( [ ...numberSequencer ], [ vocabularyCountPerInputChannel, 1 ] );

      try {
        let theLastAxisId = ( vocabularyTableShape.length - 1 ); // e.g. will be 1 for tensor2d.
        this.vocabularyTablesTensor2dArray = new Array( this.vocabularyTables.length );
        for ( let i = 0; i < this.vocabularyTables.length; ++i ) {

          // Create an embedding vocabulary table (without vocabulary id).
          const vocabularyTableTensor2dWithoutIds = tf.tensor2d( this.vocabularyTables[ i ], vocabularyTableShape );

          try {
            // Concatenate vocabulary id prefix vocabulary table.
            //
            // This is a residual connection for embedding layer. This concatenating uses some GPU memory space.
            // It, however, reduces some calculation time when predict() because the residual connection is already
            // created in advance (here).
            this.vocabularyTablesTensor2dArray[ i ] = idsTensor2d.concat( vocabularyTableTensor2dWithoutIds, theLastAxisId );
          } catch ( e ) {
            return false; // e.g. out of (GPU) memory.
          } finally {
            vocabularyTableTensor2dWithoutIds.dispose();
          }
        }

      } catch ( e ) {
        return false; // e.g. out of (GPU) memory.
      } finally {
        idsTensor2d.dispose();
      }

    } catch ( e ) {
      return false; // e.g. out of (GPU) memory.
    }

    return true;
  }

  isValid() {
    if ( this.vocabularyTablesTensor2dArray )
      if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ] ) // At least, there should be one vocabulary table.
        if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ].isValid() )  // the last vocabulary table is valid.
          return true;
    return false;
  }

  /** Release tf.tensor. */
  disposeTensors() {
    if ( this.vocabularyTablesTensor2dArray ) {
      tf.dispose( this.vocabularyTablesTensor2dArray );
      this.vocabularyTablesTensor2dArray = null;
    }
  }

  /**
   * Process the input and produce output by using the weights of this neural network layer.
   *
   * @param {tf.tensor3d} inputTensor3d
   *   A tensor3d data (e.g. height-width-color for color image, or 1-width-1 for text) with
   * this.inChannels (e.g. 4 for r-g-b-a, or 1 for text) channels.
   *
   * @return {tf.tensor3d} The predicted output as tensor3d. Return null, if failed (e.g. out of GPU memory).
   */
  predict( inputTensor3d ) {
    const predictResult = tf.tidy( "Embedding2d.Layer.predict", () => {
      try {

//!!! ...unfinished... dtype int32 resizeBilinear() result which dtype?

        // Scale input into specific size.
        let scaledInput;
        if (   ( !this.inputScaleToSize )                                       // No scaling information.
            || (   ( this.inputScaleToSize[ 0 ] == inputTensor3d.shape[ 0 ] )   // Or, already same size.
                && ( this.inputScaleToSize[ 1 ] == inputTensor3d.shape[ 1 ] ) )
           ) {
          scaledInput = inputTensor3d;
        } else {
          // Otherwise, scaling by using ( alignCorners == true ) for better looking when visualizing.
          scaledInput = inputTensor3d.resizeBilinear( inputTensor3d, this.inputScaleToSize, true );
        }

        // For example, suppose input is a color image (i.e. height-width-color tensor3d). The last
        // axis is a 4 color (r-g-b-a) channel. Splitting along the last axis (the color channel)
        // results in an array [ r, g, b, a ] which has 4 tensor3d (in fact, they should be
        // viewed as tensor1d).
        let theLastAxisId = ( scaledInput.shape.length - 1 );  // Or, ( scaledInput.rank - 1 )

        // For a 4 color (r-g-b-a) channel image, splitCount will be 4.
        //
        // This should be the same as this.inChannels.
        let splitCount = scaledInput.shape[ theLastAxisId ];

        // Extract vocabulary indices from input. (In fact, the result is still tensor3d but has only one channel.)
        const vocabularyIndicesOneChannelTensor3dArray = tf.tidy( "VocabularyIndicesArray", () => {

          // Split the last axis (of input) as many as the shape size (of the last axis) (i.e. become tensor2d).
          // And then convert to integer, so that they can be used as tf.gather()'s indices.
          return scaledInput.split( splitCount, theLastAxisId ).map( t => t.cast('int32') );
        });

        // Embedding (looking up different vocabulary tables according to channel index of vocabulary indices).
        // Every tensor3d (one channel) will be expanded to tensor3d (multiple channels).
        //
        // Note: this.vocabularyTablesTensor2dArray[] already be prefixed vocabulary id (when init()). So it
        // has residual connection in advance.
        const embeddedTensor3dArray = vocabularyIndicesOneChannelTensor3dArray.map(
          ( oneChannelTensor3d, channelIndex ) => {
            return this.vocabularyTablesTensor2dArray[ channelIndex ].gather( oneChannelTensor3d );
        });

//!!! (2020/05/16 Remarked) ...Old... Already residual connection when init().
//         // Embedding (looking up different vocabulary tables according to channel index of vocabulary indices).
//         let embeddedTensor3dArray = [];
//         for ( let i = 0; i < splitCount; ++i ) {
//           // Include the original input channel as residual connection.
//           let oneChannelTensor3d = vocabularyIndicesOneChannelTensor3dArray[ i ];
//           embeddedTensor3dArray.push( oneChannelTensor3d );
//
//           // Every tensor2d (i.e. one channel) will be expanded to tensor3d (i.e. multiple channels).
//           const multipleChannelTensor3d = this.vocabularyTablesTensor2dArray[ i ].gather( oneChannelTensor3d );
//           embeddedTensor3dArray.push( multipleChannelTensor3d );
//         }

//!!! ...unfinished... squeeze-and-excitation.

        // Concatenate along the last axis, so that it is still tensor3d but with embedded (more) channels in the last axis.
        return tf.concat( embeddedTensor3dArray, theLastAxisId );

      } catch ( e ) {
      }
    });

    return predictResult;
  }

  get byteOffsetBegin() { return this.params.defaultByteOffsetBegin; }
  get byteOffsetEnd()   { return this.vocabularyTables[ this.params.inChannels - 1 ].defaultByteOffsetEnd; }

  get inChannels()                           { return this.params.inChannels; }
  get embeddingChannelCountPerInputChannel() { return this.params.channelMultiplier; }
}
<|endoftext|>


<commit_before>var holidayNames = {
    "Australia":["mlkDay"],
    "Canada" : ["mlkday"],
    "UK" : ["mlkDay"],
    "USA" : ["mlkDay"]
    },
    countryData = {};
var $ = function(id) {
  return document.getElementById(id);
};
document.addEventListener('DOMContentLoaded', function() {
    init();
});

function init(){
    var options = [],
        index = 0,
        selectedCountry = localStorage.selectedCountry || "USA",
        counter =0,
        countrySelect = $("countrySelect");
    for(var country in holidayNames){
        options.push("<option value='"+country+"'>"+country+"</option>");
        countryData[country] = new LongWeekend(holidayNames[country]);
        if( country == selectedCountry){
         index = counter;
        }
        counter++;
    }
    countrySelect.innerHTML = options.join('');
    countrySelect.selectedIndex = index;
    countrySelect.onchange=onCountryChange;
}

function onCountryChange(){
 localStorage.selectedCountry = $("countrySelect").options[$("countrySelect").selectedIndex].innerHTML;
}

function LongWeekend(holidayList){
 this.holidayList = holidayList;
 this.startDate = Date.today().addMonths(-6);
 this.endDate = Date.today().addMonths(6);
 this.holidays = [];
}

LongWeekend.prototype.getNextLongWeekend = function(){
 if(this.holidays.length == 0){
     this.loadLongWeekends();
 }
}

LongWeekend.prototype.loadLongWeekends = function(){
    var years = this.getYears();
    this.holidayList.forEach(function(holidayFn){
       years.forEach(function(year,index,array){
           var holiday = this[holidayFn](years[index]);
           if(holiday.between(this.startDate, this.endDate)){
               this.holidays.push(holiday);
           }
       },this);
    },this);
}

LongWeekend.prototype.getYears = function(){
 var years = [],
     startYear = Number(this.startDate.toString("yyyy")),
     endYear = Number(this.endDate.toString("yyyy"));
 for(var i=startYear;i<=endYear;i++){
  years.push(i);
 }
 return years;
}

LongWeekend.prototype.mlkDay = function(year){
    return Date.parse((year-1).toString()).january().third().monday();
};
<commit_msg>Update holidays<commit_after>var holidayNames = {
    "Australia":["newYear","mlkDay"],
    "Canada" : ["newYear","familyDayBc","familyDay","victoriaDay"],
    "UK" : ["newYear","mlkDay"],
    "USA" : ["newYear","mlkDay"]
    },
    countryData = {};
var $ = function(id) {
  return document.getElementById(id);
};
document.addEventListener('DOMContentLoaded', function() {
    init();
});

function init(){
    var options = [],
        index = 0,
        selectedCountry = localStorage.selectedCountry || "USA",
        counter =0,
        countrySelect = $("countrySelect");
    for(var country in holidayNames){
        options.push("<option value='"+country+"'>"+country+"</option>");
        countryData[country] = new LongWeekend(holidayNames[country]);
        if( country == selectedCountry){
         index = counter;
        }
        counter++;
    }
    countrySelect.innerHTML = options.join('');
    countrySelect.selectedIndex = index;
    countrySelect.onchange=onCountryChange;
    showLongWeekendForCountry(selectedCountry);
}

function onCountryChange(){
    var selectedCountry = $("countrySelect").options[$("countrySelect").selectedIndex].innerHTML;
    localStorage.selectedCountry = selectedCountry;
    showLongWeekendForCountry(selectedCountry);
}

function showLongWeekendForCountry(country){
    countryData[country].getNextLongWeekend.call(countryData[country]);
}

function LongWeekend(holidayList){
 this.holidayList = holidayList;
 this.startDate = Date.today().addMonths(-6);
 this.endDate = Date.today().addMonths(6);
 this.holidays = [];
}

function Holiday(holidayName, description, link, date){
 this.holidayName = holidayName;
 this.description = description;
 this.link = link;
 this.date = date;
}

LongWeekend.prototype.getNextLongWeekend = function(){
 if(this.holidays.length == 0){
     console.log('loading data...');
     this.loadLongWeekends();
 }
    console.log(this.holidays);
}

LongWeekend.prototype.loadLongWeekends = function(){
    var years = this.getYears();
    this.holidayList.forEach(function(holidayFn){
       years.forEach(function(year,index,array){
           var holiday = this[holidayFn](years[index]);
           if(holiday.date.between(this.startDate, this.endDate)){
               this.holidays.push(holiday);
           }
       },this);
    },this);
}

LongWeekend.prototype.getYears = function(){
 var years = [],
     startYear = Number(this.startDate.toString("yyyy")),
     endYear = Number(this.endDate.toString("yyyy"));
 for(var i=startYear;i<=endYear;i++){
  years.push(i);
 }
 return years;
}

LongWeekend.prototype.mlkDay = function(year){
    return new Holiday(
        "Martin Luther King, Jr. Day",
        "An American federal holiday marking the birthday of Martin Luther King, Jr. It is observed on the third Monday of January each year, which is around the time of King's birthday, January 15.",
        "http://en.wikipedia.org/wiki/Martin_Luther_King,_Jr._Day",
        Date.parse('January 1st '+year).third().monday()
    );
};

LongWeekend.prototype.victoriaDay = function(year){
    var victoriaDay = Date.parse('May 1st '+year).final().monday();
    if(Number(victoriaDay.toString('dd'))>25){
     victoriaDay = victoriaDay.addWeeks(-1);
    }
    return new Holiday(
        "Victoria Day",
        "A federal Canadian public holiday celebrated on the last Monday before May 25, in honour of Queen Victoria's birthday.",
        "http://en.wikipedia.org/wiki/Victoria_Day",
        victoriaDay
    );
};

LongWeekend.prototype.familyDay = function(year){
    return new Holiday(
        "Family Day",
        "Observed on the third Monday of February in the provinces of AB, MB, ON, NS, PEI, and SK. In the provinces of MB, NS, and PEI, the holiday is instead termed Louis Riel Day, Nova Scotia Heritage Day, and Islander Day respectively.",
        "http://en.wikipedia.org/wiki/Family_Day_(Canada)",
        Date.parse('February 1st '+year).third().monday()
    );
};

LongWeekend.prototype.familyDayBc = function(year){
    return new Holiday(
        "Family Day(BC)",
        "A statutory holiday occurring on second Monday in February observed in BC",
        "http://en.wikipedia.org/wiki/Family_Day_(Canada)",
        Date.parse('February 1st '+year).second().monday()
    );
};

LongWeekend.prototype.newYear = function(year){
    return new Holiday(
        "New Year's Day",
        "The first day of the year on the modern Gregorian calendar as well as the Julian calendar used in the Roman Empire since 45 BC.",
        "https://en.wikipedia.org/wiki/New_Year%27s_Day",
        Date.parse('January 1st '+year)
    );
};<|endoftext|>