text:
/**
 * @requires SuperMap/Renderer.js
 */

/**
 * Class: SuperMap.Renderer.AnimatorCanvas
 * 基于 2D 'canvas' 的一种渲染,用于渲染矢量动画效果
 * 支持点线面的渐变效果
 *
 * Inherits from:
 *  - <SuperMap.Renderer>
 */
SuperMap.Renderer.AnimatorCanvas = SuperMap.Class(SuperMap.Renderer, {

    /**
     * Property: hitDetection
     * {Boolean} 是否允许监测 features,默认为 true(现在还不支持事件,没得必要绘制)。
     * (其实就是是否支持选择事件)
     */
    hitDetection: true,

    /**
     * Property: hitOverflow
     * {Number} The method for converting feature identifiers to color values
     *     supports 16777215 sequential values.  Two features cannot be
     *     predictably detected if their identifiers differ by more than this
     *     value.  The hitOverflow allows for bigger numbers (but the
     *     difference in values is still limited).
     *     好像是用于控制颜色值转换为featureid时的模糊处理
     */
    hitOverflow: 0,

    /**
     * Property: canvas
     * {Canvas} The canvas context object.
     */
    canvas: null,

    /**
     * Property: features
     * {Object} Internal object of feature/style pairs for use in redrawing the layer.
     */
    features: null,

    /**
     * Property: londingimgs
     * {Object} 处于下载中的图片
     */
    londingimgs:{},
    /**
     * Property: londedimgs
     * {Object} 下载完毕的图片
     */
    londedimgs:{},
    /**
     * Property: layer
     * {<SuperMap.Layer.AnimatorVector>} 存放图层
     */
    layer:null,
    /**
     * Constructor: SuperMap.Renderer.AnimatorCanvas
     * 动画渲染类的构造函数
     * (不允许用户初始化)
     */
    initialize: function(containerID, options,layer) {
        this.layer = layer;
        SuperMap.Renderer.prototype.initialize.apply(this, arguments);
        this.root = document.createElement("canvas");
        this.container.appendChild(this.root);
        this.canvas = this.root.getContext("2d");
        this.features = {};
        //事件
        if (this.hitDetection) {
            this.hitCanvas = document.createElement("canvas");
            this.hitContext = this.hitCanvas.getContext("2d");
        }
    },

    /**
     * Method: setExtent
     * Set the visible part of the layer.
     *
     * Parameters:
     * extent - {<OpenLayers.Bounds>}
     * resolutionChanged - {Boolean}
     *
     * Returns:
     * {Boolean} true to notify the layer that the new extent does not exceed
     *     the coordinate range, and the features will not need to be redrawn.
     *     False otherwise.
     */
    setExtent: function(extent, resolutionChanged) {
        SuperMap.Renderer.prototype.setExtent.apply(this, arguments);
        // always redraw features
        return false;
    },

    /**
     * Method: eraseGeometry
     * Erase a geometry from the renderer. Because the Canvas renderer has
     *     'memory' of the features that it has drawn, we have to remove the
     *     feature so it doesn't redraw.
     *
     * Parameters:
     * geometry - {<SuperMap.Geometry>}
     * featureId - {String}
     */
    eraseGeometry: function(geometry, featureId) {
        this.eraseFeatures(this.features[featureId][0]);
    },

    /**
     * APIMethod: supported
     * 判断浏览器是否支持动画渲染
     * Returns:
     * {Boolean} 返回浏览器是否支持动画的渲染(也就是canvas渲染)
     */
    supported: function() {
        var canvas = document.createElement("canvas");
        return !!canvas.getContext;
    },

    /**
     * Method: setSize
     * Sets the size of the drawing surface.
     *
     * Once the size is updated, redraw the canvas.
     *
     * Parameters:
     * size - {<SuperMap.Size>}
     */
    setSize: function(size) {
        this.size = size.clone();
        var root = this.root;
        root.style.width = size.w + "px";
        root.style.height = size.h + "px";
        root.width = size.w;
        root.height = size.h;
        this.resolution = null;
        if (this.hitDetection) {
            var hitCanvas = this.hitCanvas;
            hitCanvas.style.width = size.w + "px";
            hitCanvas.style.height = size.h + "px";
            hitCanvas.width = size.w;
            hitCanvas.height = size.h;
        }
    },

    /**
     * Method: isInnerExtent
     * 判断传入的Geometry是否需要被绘制
     *
     * Parameters:
     * geo - {<SuperMap.Geometry>}
     * style - {<Object>}
     *
     * Returns:
     * {Boolean} 返回Geoemtry是否绘制,它的geometry超出返回或者未定义、不显示等情况都不绘制
     */
    isNeedToDraw: function(geo, style) {
        var rendered;
        geo.calculateBounds();
        var bounds = geo.getBounds();
        rendered = (style.display !== "none") && !!bounds &&
            bounds.intersectsBounds(this.extent);
        return rendered;
    },

    /**
     * Method: createGeometry
     * 根据传入参数类型,创建相应类型的Geometry
     *
     * Parameters:
     * geoType - {<String>}
     *
     * Returns:
     * {SuperMap.Geometry} 返回创建好的Geometry
     */
    createGeometry: function(geoType){
        var geometry;
        switch (geoType) {
            case "SuperMap.Geometry.Collection": {
                geometry = new SuperMap.Geometry.Collection();
                break;
            }
            case "SuperMap.Geometry.MultiPoint": {
                geometry = new SuperMap.Geometry.MultiPoint();
                break;
            }
            case "SuperMap.Geometry.MultiLineString": {
                geometry = new SuperMap.Geometry.MultiLineString();
                break;
            }
            case "SuperMap.Geometry.MultiPolygon": {
                geometry = new SuperMap.Geometry.MultiPolygon();
                break;
            }
            default: {
                break;
            }
        }
        return geometry;
    },

    /**
     * Method: removeById
     *从数组中移除指定ID的元素
     *
     * Parameters:
     * arr - {<Array>}
     * id - {<String>}
     */
    removeById: function(arr, id) {
        var featureId= this.features[id][0].id;
        for(var i=0; i<arr.length; i++) {
            if(arr[i][0].id == featureId) {
                arr.splice(i, 1);
                break;
            }
        }
    },

    /**
     * Method: drawFeature
     * Draw the feature. Stores the feature in the features list,
     * then redraws the layer.
     *
     * Parameters:
     * feature - {<SuperMap.Feature.Vector>}
     * style - {<Object>}
     *
     * Returns:
     * {Boolean} 返回feature是否绘制,它的geometry超出返回或者未定义、不显示等情况都不绘制
     */
    drawFeature: function(feature, style, option) {
        var rendered;
        if (feature.geometry) {
            this.features[feature.id] = [feature, style, option];
            //获取style
            style = this.applyDefaultSymbolizer(style || feature.style);
            //如果display===none或者bounds不在extent之内,都不绘制(没必要绘制)
            //feature.geometry.calculateBounds();//重新计算一下bounds
            var recordGeometry = this.createGeometry(feature.geometry.CLASS_NAME);
            var renderedGeometry = this.drawGeometry(
                feature.geometry,
                style,
                feature.id,
                feature.frontFeature && feature.frontFeature.geometry,
                feature.backFeature && feature.backFeature.geometry,
                recordGeometry
            );
            if(!!renderedGeometry) {
                rendered = feature.clone();
                rendered.geometry = renderedGeometry;
            } else {
                this.removeById(this.features, feature.id);
            }
        }
        return rendered;
    },

    /**
     * Method: drawGeometry
     * Used when looping (in redraw) over the features; draws
     * the canvas.
     *
     * Parameters:
     * geometry - {<SuperMap.Geometry>}
     * style - {Object}
     */
    drawGeometry: function(geometry, style, featureId,frontGeometry,backGeometry, recordGeometry) {
        var className = geometry.CLASS_NAME;
        if ((className === "SuperMap.Geometry.Collection") ||
            (className === "SuperMap.Geometry.MultiPoint") ||
            (className === "SuperMap.Geometry.MultiLineString") ||
            (className === "SuperMap.Geometry.MultiPolygon")) {
            for (var i = 0; i < geometry.components.length; i++) {
                var smoothedGeometry = this.drawGeometry(
                    geometry.components[i],
                    style,
                    featureId,
                    frontGeometry && frontGeometry.components[i],
                    backGeometry && backGeometry.components[i],
                    recordGeometry
                );
                recordGeometry.components[i] = smoothedGeometry;
            }
            return recordGeometry;
        }
        switch (geometry.CLASS_NAME) {
            case "SuperMap.Geometry.Point":
                //这里进行位置转换,然后再去调用drawPoint
                //此处的geometry还为经过平滑处理
                var points = this.smoothConvertPoint(geometry,frontGeometry,backGeometry,featureId);
                //将处理过的点再次进行绘制
                if(this.isNeedToDraw(points[0], style)) {
                    this.drawPoint(points[0], style, featureId,points[1],points[2]);
                    return points[0];
                } else {
                    return geometry;
                }
            case "SuperMap.Geometry.LineString":
            //当对象为路由对象时,进行判断并绘制
            case "SuperMap.REST.Route":
                var lineStrings = this.smoothConvertLine(geometry,frontGeometry,backGeometry,featureId);
                if(this.isNeedToDraw(lineStrings[0], style)) {
                    this.drawLineString(lineStrings[0], style, featureId,lineStrings[1],lineStrings[2]);
                    return lineStrings[0];
                } else {
                    return geometry;
                }
            case "SuperMap.Geometry.LinearRing":
                var lineRings = this.smoothConvertLine(geometry,frontGeometry,backGeometry,featureId);
                if(this.isNeedToDraw(lineRings[0], style)) {
                    this.drawLinearRing(lineRings[0], style, featureId,lineRings[1],lineRings[2]);
                    return lineRings[0];
                } else {
                    return geometry;
                }
            case "SuperMap.Geometry.Polygon":
                var polygons = this.smoothConvertPolygon(geometry,frontGeometry,backGeometry,featureId);
                if(this.isNeedToDraw(polygons[0], style)) {
                    this.drawPolygon(polygons[0], style, featureId,polygons[1],polygons[2]);
                    return polygons[0];
                } else {
                    return geometry;
                }
//            case "SuperMap.Geometry.Rectangle":
//                this.drawRectangle(geometry, style, featureId);
//                break;
            default:
                break;
        }
    },
    /**
     * APIMethod: smoothConvertPoint
     * 对点进行特殊转换以实现希望的效果
     * (此方法用于继承重写)
     *
     * Parameters:
     * geometry - {<SuperMap.Geometry>}   与当前时刻最接近的即将绘制的geometry
     * frontGeometry - {<SuperMap.Geometry>}  geometry 的前一个数据(同一实物)
     * backGeometry - {<SuperMap.Geometry>}  geometry 的后一个数据(同一实物)
     * featureId - {String} geometry 所对应的feature的id
     *
     * Returns:
     * {Array} 返回 [即将需要绘制的 geometry,geometry 的前一个数据,geometry 的后一个数据]
     */
    smoothConvertPoint:function(geometry,frontGeometry,backGeometry,featureId){
        var options = this.features[featureId][2];
        if(options && options.smooth && options.smooth[0])
        {
            var geo = geometry.clone();
            geo.x = frontGeometry.x + (geo.x - frontGeometry.x)*options.smooth[1];
            geo.y = frontGeometry.y + (geo.y - frontGeometry.y)*options.smooth[1];

            return [geo,frontGeometry,geometry];
        }
        else
        {
            return [geometry,frontGeometry,backGeometry];
        }
    },
    //记录线的改变,用于提高效率
    linesIndex:[],
    /**
     * APIMethod: smoothConvertLine
     * 对线进行特殊转换以实现希望的效果
     * (此方法用于继承重写)
     *
     * Parameters:
     * geometry - {<SuperMap.Geometry>}   与当前时刻最接近的即将绘制的geometry
     * frontGeometry - {<SuperMap.Geometry>}  geometry 的前一个数据(同一实物)
     * backGeometry - {<SuperMap.Geometry>}  geometry 的后一个数据(同一实物)
     * featureId - {String} geometry 所对应的feature的id
     *
     * Returns:
     * {Array} 返回 [即将需要绘制的 geometry,geometry 的前一个数据,geometry 的后一个数据]
     */
    smoothConvertLine:function(geometry,frontGeometry,backGeometry,featureId){
        var options = this.features[featureId][2];
        if(options && options.smooth && options.smooth[0])
        {
            var geo = geometry.clone();
            //如果之前计算过,存在了,就直接使用
            if(this.linesIndex[featureId] && this.linesIndex[featureId][geometry.id])
            {
                var ar = this.linesIndex[featureId][geometry.id];
                if(ar[0] === frontGeometry.id)
                {
                    var arr1_ = ar[1];
                    var arr2_ = ar[2];
                    var arr_ = [];
                    for(var k= 0,len_=arr1_.length;k<len_;k++)
                    {
                        arr_.push(
                            new SuperMap.Geometry.Point(
                                arr1_[k].x + (arr2_[k].x - arr1_[k].x)*options.smooth[1],
                                arr1_[k].y + (arr2_[k].y - arr1_[k].y)*options.smooth[1]
                                )
                        );
                    }
                    geo.components = arr_;

                    return [geo,frontGeometry,geometry];
                }
            }
            //否则需要计算
            var geo1 = frontGeometry.clone();
            var geo2 = geometry.clone();
            var len1 = geo1.getLength();
            var len2 = geo2.getLength();
            var arr1 = [geo1.components[0].clone()];
            var arr2 = [geo2.components[0].clone()];
            var arr = [new SuperMap.Geometry.Point(
                geo1.components[0].x + (geo2.components[0].x - geo1.components[0].x)*options.smooth[1],
                geo1.components[0].y + (geo2.components[0].y - geo1.components[0].y)*options.smooth[1]
            )];
            var dis1 = 0;
            var dis2 = 0;
            var i= 1,j=1;
            for(;i<geo1.components.length || j<geo2.components.length;)
            {
                var po1 = geo1.components[i];
                var po2 = geo2.components[j];
                var po1_f = geo1.components[i-1];
                var po2_f = geo2.components[j-1];

                var d1 = po1.distanceTo(po1_f);
                var d2 = po2.distanceTo(po2_f);
                if((dis1+d1)/len1>(dis2+d2)/len2)
                {
                    //出第二条线的节点在第一条线上的位置离上一个节点的模
                    var mo1 = len1*(dis2+d2)/len2-dis1;
                    dis2+=d2;
                    //计算位置
                    var x = po1_f.x + (po1.x-po1_f.x)*mo1/d1;
                    var y = po1_f.y + (po1.y-po1_f.y)*mo1/d1;
                    var po2_ = new SuperMap.Geometry.Point(x,y);
                    arr1.push(po2_);
                    arr2.push(po2.clone());
                    arr.push(new SuperMap.Geometry.Point(
                        po2_.x + (po2.x - po2_.x)*options.smooth[1],
                        po2_.y + (po2.y - po2_.y)*options.smooth[1]
                    ));
                    if((j===geo2.components.length-1) && (i===geo1.components.length-1))
                    {
                        break;
                    }
                    if(j<geo2.components.length-1){
                        j++;
                    }
                }
                else if((dis1+d1)/len1<(dis2+d2)/len2)
                {
                    var mo2 = len2*(dis1+d1)/len1-dis2;
                    dis1+=d1;
                    //计算位置
                    var x = po2_f.x + (po2.x-po2_f.x)*mo2/d2;
                    var y = po2_f.y + (po2.y-po2_f.y)*mo2/d2;
                    var po1_ = new SuperMap.Geometry.Point(x,y);
                    arr1.push(po1.clone());
                    arr2.push(po1_);
                    arr.push(new SuperMap.Geometry.Point(
                        po1.x + (po1_.x - po1.x)*options.smooth[1],
                        po1.y + (po1_.y - po1.y)*options.smooth[1]
                    ));
                    if((j===geo2.components.length-1) && (i===geo1.components.length-1))
                    {
                        break;
                    }
                    if(i<geo1.components.length-1){
                        i++;
                    }
                }
                else
                {
                    arr1.push(po1.clone());
                    arr2.push(po2.clone());
                    arr.push(new SuperMap.Geometry.Point(
                        po1.x + (po2.x - po1.x)*options.smooth[1],
                        po1.y + (po2.y - po1.y)*options.smooth[1]
                    ));
                    if((j===geo2.components.length-1) && (i===geo1.components.length-1))
                    {
                        break;
                    }
                    if(i<geo1.components.length-1){
                        i++;
                    }
                    if(j<geo2.components.length-1){
                        j++;
                    }
                }
            }
            geo.components = arr;
            if(!this.linesIndex[featureId])
            {
                this.linesIndex[featureId] = {};
            }
            //存起来
            this.linesIndex[featureId][geometry.id] = [frontGeometry.id,arr1,arr2];
            return [geo,frontGeometry,geometry];
        }
        else
        {
            return [geometry,frontGeometry,backGeometry];
        }
    },
    //记录面的改变,用于提高效率
    polygonsIndex:[],
    /**
     * APIMethod: smoothConvertPolygon
     * 对面进行特殊转换以实现希望的效果
     * (此方法用于继承重写)
     *
     * Parameters:
     * geometry - {<SuperMap.Geometry>}   与当前时刻最接近的即将绘制的geometry
     * frontGeometry - {<SuperMap.Geometry>}  geometry 的前一个数据(同一实物)
     * backGeometry - {<SuperMap.Geometry>}  geometry 的后一个数据(同一实物)
     * featureId - {String} geometry 所对应的feature的id
     *
     * Returns:
     * {Array} 返回 [即将需要绘制的 geometry,geometry 的前一个数据,geometry 的后一个数据]
     */
    smoothConvertPolygon:function(geometry,frontGeometry,backGeometry,featureId){
        var options = this.features[featureId][2];
        if(options && options.smooth && options.smooth[0])
        {
            var geo = geometry.clone();
            //如果之前计算过,存在了,就直接使用
            if(this.polygonsIndex[featureId] && this.polygonsIndex[featureId][geometry.id])
            {
                var arh = this.polygonsIndex[featureId][geometry.id];
                //和之前的情况一样才使用,否则跳过,还是需要计算
                if(arh[0] === frontGeometry.id)
                {
                    //一个面里面可能有多个环
                    for(var n = 0;n<arh[1].length;n++)
                    {
                        var ar = arh[1][n];
                        var arr1_ = ar[0];
                        var arr2_ = ar[1];
                        var arr_ = [];
                        for(var k= 0,len_=arr1_.length;k<len_;k++)
                        {
                            arr_.push(
                                new SuperMap.Geometry.Point(
                                    arr1_[k].x + (arr2_[k].x - arr1_[k].x)*options.smooth[1],
                                    arr1_[k].y + (arr2_[k].y - arr1_[k].y)*options.smooth[1]
                                )
                            );
                        }
                        geo.components[n].components = arr_;

                    }
                    return [geo,frontGeometry,geometry];
                }

            }

            if(!this.polygonsIndex[featureId])
            {
                this.polygonsIndex[featureId] = {};
            }

            //否则需要计算
            var geo1 = frontGeometry.clone();
            var geo2 = geometry.clone();
            //记录面里面所有的环数组
            var arrHuan = [];
            for(var m = 0;m<geo2.components.length;m++)
            {
                var len1 = geo1.components[m].getLength();
                var len2 = geo2.components[m].getLength();
                var arr1 = [geo1.components[m].components[0].clone()];
                var arr2 = [geo2.components[m].components[0].clone()];
                var arr = [new SuperMap.Geometry.Point(
                    geo1.components[m].components[0].x + (geo2.components[m].components[0].x - geo1.components[m].components[0].x)*options.smooth[1],
                    geo1.components[m].components[0].y + (geo2.components[m].components[0].y - geo1.components[m].components[0].y)*options.smooth[1]
                )];
                var dis1 = 0;
                var dis2 = 0;
                var i= 1,j=1;
                for(;i<geo1.components[m].components.length || j<geo2.components[m].components.length;)
                {
                    var po1 = geo1.components[m].components[i];
                    var po2 = geo2.components[m].components[j];
                    var po1_f = geo1.components[m].components[i-1];
                    var po2_f = geo2.components[m].components[j-1];

                    var d1 = po1.distanceTo(po1_f);
                    var d2 = po2.distanceTo(po2_f);
                    if((dis1+d1)/len1>(dis2+d2)/len2)
                    {
                        //出第二条线的节点在第一条线上的位置离上一个节点的模
                        var mo1 = len1*(dis2+d2)/len2-dis1;
                        dis2+=d2;
                        //计算位置
                        var x = po1_f.x + (po1.x-po1_f.x)*mo1/d1;
                        var y = po1_f.y + (po1.y-po1_f.y)*mo1/d1;
                        var po2_ = new SuperMap.Geometry.Point(x,y);
                        arr1.push(po2_);
                        arr2.push(po2.clone());
                        arr.push(new SuperMap.Geometry.Point(
                            po2_.x + (po2.x - po2_.x)*options.smooth[1],
                            po2_.y + (po2.y - po2_.y)*options.smooth[1]
                        ));
                        if((j===geo2.components[m].components.length-1) && (i===geo1.components[m].components.length-1))
                        {
                            break;
                        }
                        if(j<geo2.components[m].components.length-1){
                            j++;
                        }
                    }
                    else if((dis1+d1)/len1<(dis2+d2)/len2)
                    {
                        var mo2 = len2*(dis1+d1)/len1-dis2;
                        dis1+=d1;
                        //计算位置
                        var x = po2_f.x + (po2.x-po2_f.x)*mo2/d2;
                        var y = po2_f.y + (po2.y-po2_f.y)*mo2/d2;
                        var po1_ = new SuperMap.Geometry.Point(x,y);
                        arr1.push(po1.clone());
                        arr2.push(po1_);
                        arr.push(new SuperMap.Geometry.Point(
                            po1.x + (po1_.x - po1.x)*options.smooth[1],
                            po1.y + (po1_.y - po1.y)*options.smooth[1]
                        ));
                        if((j===geo2.components[m].components.length-1) && (i===geo1.components[m].components.length-1))
                        {
                            break;
                        }
                        if(i<geo1.components[m].components.length-1){
                            i++;
                        }
                    }
                    else
                    {
                        arr1.push(po1.clone());
                        arr2.push(po2.clone());
                        arr.push(new SuperMap.Geometry.Point(
                            po1.x + (po2.x - po1.x)*options.smooth[1],
                            po1.y + (po2.y - po1.y)*options.smooth[1]
                        ));
                        if((j===geo2.components[m].components.length-1) && (i===geo1.components[m].components.length-1))
                        {
                            break;
                        }
                        if(i<geo1.components[m].components.length-1){
                            i++;
                        }
                        if(j<geo2.components[m].components.length-1){
                            j++;
                        }
                    }
                }
                geo.components[m].components = arr;
                arrHuan.push([arr1,arr2]);
            }
            //存起来
            this.polygonsIndex[featureId][geometry.id] = [frontGeometry.id,arrHuan];

            return [geo,frontGeometry,geometry];
        }
        else
        {
            return [geometry,frontGeometry,backGeometry];
        }
    },
    /**
     * Method: drawExternalGraphic
     * Called to draw External graphics.
     *
     * Parameters:
     * geometry - {<SuperMap.Geometry>}
     * style    - {Object}
     * featureId - {String}
     */
    drawExternalGraphic: function(geometry, style, featureId) {
        var t = this;
        var feature = t.features[featureId][0];
        var frontFeature = feature.frontFeature;

        var img;
        if(this.londedimgs[style.externalGraphic])
        {
            img = this.londedimgs[style.externalGraphic];
            var width = style.graphicWidth || style.graphicHeight;
            var height = style.graphicHeight || style.graphicWidth;
            width = width ? width : style.pointRadius * 2;
            height = height ? height : style.pointRadius * 2;
            var xOffset = (style.graphicXOffset != undefined) ?
                style.graphicXOffset : -(0.5 * width);
            var yOffset = (style.graphicYOffset != undefined) ?
                style.graphicYOffset : -(0.5 * height);

            var opacity = style.graphicOpacity || style.fillOpacity,
                allowRotate=style.allowRotate;
            var pt = t.getLocalXY(geometry);
            var p0 = pt[0];
            var p1 = pt[1];
            var rotation=0;
            if(allowRotate){
                if(frontFeature)
                {
                    var geo_f = frontFeature.geometry;
                    rotation = Math.PI/2-Math.atan2(geometry.y-geo_f.y,geometry.x-geo_f.x);
                }
                else
                {
                    if(feature.backFeature && feature.backFeature.geometry)
                    {
                        var geo_b =  feature.backFeature.geometry;
                        rotation = Math.PI/2-Math.atan2(geo_b.y-geometry.y,geo_b.x-geometry.x);
                    }
                    else
                    {
                        rotation = 0;
                    }
                }
            }
            if(!isNaN(p0) && !isNaN(p1)) {
                var x = (p0 + xOffset) | 0;
                var y = (p1 + yOffset) | 0;
                var canvas = t.canvas;
                canvas.globalAlpha = opacity;
                var factor = SuperMap.Renderer.Canvas.drawImageScaleFactor ||
                    (SuperMap.Renderer.Canvas.drawImageScaleFactor =
                        /android 2.1/.test(navigator.userAgent.toLowerCase()) ?
                            // 320 is the screen width of the G1 phone, for
                            // which drawImage works out of the box.
                            320 / window.screen.width : 1
                        );
                if(rotation != 0)
                {
                    canvas.save();
                    canvas.translate(p0, p1);
                    canvas.rotate(rotation);
                    canvas.drawImage(
                        img, (x-p0)*factor, (y-p1)*factor, width*factor, height*factor
                    );
                    canvas.restore();
                }
                else
                {
                    canvas.drawImage(
                        img, x*factor, y*factor, width*factor, height*factor
                    );
                }
                if (t.hitDetection) {
                    t.setHitContextStyle("fill", featureId);
                    t.hitContext.fillRect(x, y, width, height);
                }
            }
            return;
        }
        //判断
        if(this.londingimgs[style.externalGraphic]){
            this.londingimgs[style.externalGraphic].onload = function(){
                return false;
            }
        }

        var img = new Image();
        this.londingimgs[style.externalGraphic] = img;

//        if (style.graphicTitle) {
//            img.title = style.graphicTitle;
//        }

        var onLoad = function() {
            var featureId = this.featureId;
            var geometry = this.geometry;
            var style = this.style;
            var img = this.img;

            t.londingimgs[style.externalGraphic] = null;
            t.londedimgs[style.externalGraphic] = img;
            if(!t.features[featureId]) {
                return;
            }
            var feature = t.features[featureId][0];
            var frontFeature = feature.frontFeature;

            var width = style.graphicWidth || style.graphicHeight;
            var height = style.graphicHeight || style.graphicWidth;
            width = width ? width : style.pointRadius * 2;
            height = height ? height : style.pointRadius * 2;
            var xOffset = (style.graphicXOffset != undefined) ?
                style.graphicXOffset : -(0.5 * width);
            var yOffset = (style.graphicYOffset != undefined) ?
                style.graphicYOffset : -(0.5 * height);

            var opacity = style.graphicOpacity || style.fillOpacity;

            var pt = t.getLocalXY(geometry);
            var p0 = pt[0];
            var p1 = pt[1];
            var rotation;
            if(frontFeature)
            {
                var geo_f = frontFeature.geometry;
                rotation = Math.PI/2-Math.atan2(geometry.y-geo_f.y,geometry.x-geo_f.x);
            }
            else
            {
                if(feature.backFeature)
                {
                    var geo_b =  feature.backFeature.geometry;
                    rotation = Math.PI/2-Math.atan2(geo_b.y-geometry.y,geo_b.x-geometry.x);
                }
                else
                {
                    rotation = 0;
                }
            }
            if(!isNaN(p0) && !isNaN(p1)) {
                var x = (p0 + xOffset) | 0;
                var y = (p1 + yOffset) | 0;
                var canvas = t.canvas;
                canvas.globalAlpha = opacity;
                //android 2.1特殊情况
                var factor = SuperMap.Renderer.Canvas.drawImageScaleFactor ||
                    (SuperMap.Renderer.Canvas.drawImageScaleFactor =
                        /android 2.1/.test(navigator.userAgent.toLowerCase()) ?
                            // 320 is the screen width of the G1 phone, for
                            // which drawImage works out of the box.
                            320 / window.screen.width : 1
                    );
                if(rotation != 0)
                {
                    canvas.save();
                    canvas.translate(p0, p1);
                    canvas.rotate(rotation);
                    canvas.drawImage(
                        img, (x-p0)*factor, (y-p1)*factor, width*factor, height*factor
                    );
                    canvas.restore();
                }
                else
                {
                    canvas.drawImage(
                        img, x*factor, y*factor, width*factor, height*factor
                    );
                }

                if (t.hitDetection) {
                    t.setHitContextStyle("fill", featureId);
                    t.hitContext.fillRect(x, y, width, height);
                }
            }
        };

        img.onload = SuperMap.Function.bind(onLoad, {
            featureId:featureId,
            geometry:geometry.clone(),
            style:style,
            img:img
        });
        img.src = style.externalGraphic;
    },

    /**
     * Method: setCanvasStyle
     * Prepare the canvas for drawing by setting various global settings.
     *
     * Parameters:
     * type - {String} one of 'stroke', 'fill', or 'reset'
     * style - {Object} Symbolizer hash
     */
    setCanvasStyle: function(type, style) {
        if (type === "fill") {
            this.canvas.globalAlpha = style['fillOpacity'];
            this.canvas.fillStyle = style['fillColor'];
        } else if (type === "stroke") {
            this.canvas.globalAlpha = style['strokeOpacity'];
            this.canvas.lineCap = style['strokeLinecap'];
            this.canvas.strokeStyle = style['strokeColor'];
            this.canvas.lineWidth = style['strokeWidth'];
        } else {
            this.canvas.globalAlpha = 0;
            this.canvas.lineWidth = 1;
        }
    },

    /**
     * Method: featureIdToHex
     * Convert a feature ID string into an RGB hex string.
     *
     * Parameters:
     * featureId - {String} Feature id
     *
     * Returns:
     * {String} RGB hex string.
     */
    featureIdToHex: function(featureId) {

        var id = Number(featureId.split("_").pop()) + 1; // zero for no feature
        if (id >= 16777216) {
            this.hitOverflow = id - 16777215;
            id = id % 16777216 + 1;
        }
        var hex = "000000" + id.toString(16);
        var len = hex.length;
        hex = "#" + hex.substring(len-6, len);
        return hex;
    },

    /**
     * Method: setHitContextStyle
     * Prepare the hit canvas for drawing by setting various global settings.
     *
     * Parameters:
     * type - {String} one of 'stroke', 'fill', or 'reset'
     * featureId - {String} The feature id.
     * symbolizer - {<SuperMap.Symbolizer>} The symbolizer.
     */
    setHitContextStyle: function(type, featureId, symbolizer) {
        var hex = this.featureIdToHex(featureId);
        if (type === "fill") {
            this.hitContext.globalAlpha = 1.0;
            this.hitContext.fillStyle = hex;
        } else if (type === "stroke") {
            this.hitContext.globalAlpha = 1.0;
            this.hitContext.strokeStyle = hex;
            // bump up stroke width to deal with antialiasing
            this.hitContext.lineWidth = symbolizer.strokeWidth + 2;
        } else {
            this.hitContext.globalAlpha = 0;
            this.hitContext.lineWidth = 1;
        }
    },
    /**
     * APIMethod: drawPoint
     * 此方法具体实现在canvas上绘制点
     * (用于子类重写)
     *
     * Parameters:
     * geometry - {<SuperMap.Geometry>} 需要绘制的点
     * style    - {Object}  绘制时的style
     * featureId - {String}  geometry 对应的 feature 的 id
     * frontGeometry - {<SuperMap.Geometry>}  geometry 的前一个数据(同一实物)
     * backGeometry - {<SuperMap.Geometry>}  geometry 的后一个数据(同一实物)
     */
    drawPoint: function(geometry, style, featureId,frontGeometry,backGeometry) {
        var me = this;
        if(style.graphic !== false) {
            if(style.externalGraphic) {
                me.drawExternalGraphic(geometry, style, featureId);
            } else {
                var pt = me.getLocalXY(geometry);
                var p0 = pt[0];
                var p1 = pt[1];
                if(!isNaN(p0) && !isNaN(p1)) {
                    var twoPi = Math.PI*2;
                    var radius = style.pointRadius;
                    if(style.fill !== false) {
                        me.setCanvasStyle("fill", style);
                        me.canvas.beginPath();
                        me.canvas.arc(p0, p1, radius, 0, twoPi, true);
                        me.canvas.fill();
                        if (this.hitDetection) {
                            this.setHitContextStyle("fill", featureId, style);
                            this.hitContext.beginPath();
                            this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
                            this.hitContext.fill();
                        }
                    }

                    if(style.stroke !== false) {
                        this.setCanvasStyle("stroke", style);
                        this.canvas.beginPath();
                        this.canvas.arc(p0, p1, radius, 0, twoPi, true);
                        this.canvas.stroke();
                        if (this.hitDetection) {
                            this.setHitContextStyle("stroke", featureId, style);
                            this.hitContext.beginPath();
                            this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
                            this.hitContext.stroke();
                        }
                        this.setCanvasStyle("reset");
                    }
                }
            }
        }

    },

    /**
     * APIMethod: drawLineString
     * 此方法具体实现在canvas上绘制线
     * (用于子类重写)
     * Parameters:
     * geometry - {<SuperMap.Geometry>} 需要绘制的线
     * style    - {Object}  绘制时的style
     * featureId - {String}  geometry 对应的 feature 的 id
     * frontGeometry - {<SuperMap.Geometry>}  geometry 的前一个数据(同一实物)
     * backGeometry - {<SuperMap.Geometry>}  geometry 的后一个数据(同一实物)
     */
    drawLineString: function(geometry, style, featureId,frontGeometry,backGeometry) {
        style = SuperMap.Util.applyDefaults({fill: false}, style);
        this.drawLinearRing(geometry, style, featureId);
    },

    /**
     * Method: drawLinearRing
     * This method is only called by the renderer itself.
     *
     * Parameters:
     * geometry - {<SuperMap.Geometry>}
     * style    - {Object}
     * featureId - {String}
     */
    drawLinearRing: function(geometry, style, featureId) {
        //绘制面填充
        if (style.fill !== false) {
            this.setCanvasStyle("fill", style);
            this.renderPath(this.canvas, geometry, style, featureId, "fill");
            if (this.hitDetection) {
                this.setHitContextStyle("fill", featureId, style);
                this.renderPath(this.hitContext, geometry, style, featureId, "fill");
            }
        }
        //绘制边缘线
        if (style.stroke !== false) {
            this.setCanvasStyle("stroke", style);
            this.renderPath(this.canvas, geometry, style, featureId, "stroke");
            if (this.hitDetection) {
                this.setHitContextStyle("stroke", featureId, style);
                this.renderPath(this.hitContext, geometry, style, featureId, "stroke");
            }
        }
        this.setCanvasStyle("reset");
    },

    /**
     * Method: renderPath
     * Render a path with stroke and optional fill.
     */
    renderPath: function(context, geometry, style, featureId, type) {
        var widthFactor=1;
        if(typeof context.setLineDash==="function"){
            var dasharray=this.dashStyle(style,widthFactor);
            context.setLineDash(dasharray);
        }
        var components = geometry.components;
        var len = components.length;
        context.beginPath();
        var start = this.getLocalXY(components[0]);
        var x = start[0];
        var y = start[1];
        if (!isNaN(x) && !isNaN(y)) {
            context.moveTo(start[0], start[1]);
            for (var i=1; i<len; ++i) {
                var pt = this.getLocalXY(components[i]);
                context.lineTo(pt[0], pt[1]);
            }
            if (type === "fill") {
                context.fill();
            } else {
                context.stroke();
            }
        }
        if(typeof context.setLineDash==="function"){
            context.setLineDash([]);
        }
    },

    /**
     * APIMethod: drawPolygon
     * 此方法具体实现在canvas上绘制面
     * (用于子类重写)
     * Parameters:
     * geometry - {<SuperMap.Geometry>} 需要绘制的面
     * style    - {Object}  绘制时的style
     * featureId - {String}  geometry 对应的 feature 的 id
     * frontGeometry - {<SuperMap.Geometry>}  geometry 的前一个数据(同一实物)
     * backGeometry - {<SuperMap.Geometry>}  geometry 的后一个数据(同一实物)
     */
    drawPolygon: function(geometry, style, featureId,frontGeometry,backGeometry) {
        var components = geometry.components;
        var len = components.length;
        this.drawLinearRing(components[0], style, featureId);
        // erase inner rings
        for (var i=1; i<len; ++i) {
            /**
             * Note that this is overly agressive.  Here we punch holes through
             * all previously rendered features on the same canvas.  A better
             * solution for polygons with interior rings would be to draw the
             * polygon on a sketch canvas first.  We could erase all holes
             * there and then copy the drawing to the layer canvas.
             * TODO: http://trac.osgeo.org/SuperMap/ticket/3130
             */
            this.canvas.globalCompositeOperation = "destination-out";
            if (this.hitDetection) {
                this.hitContext.globalCompositeOperation = "destination-out";
            }
            this.drawLinearRing(
                components[i],
                SuperMap.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style),
                featureId
            );
            this.canvas.globalCompositeOperation = "source-over";
            if (this.hitDetection) {
                this.hitContext.globalCompositeOperation = "source-over";
            }
            this.drawLinearRing(
                components[i],
                SuperMap.Util.applyDefaults({fill: false}, style),
                featureId
            );
        }
    },
    /**
     * Method: drawRectangle
     * 该方法用于绘制矩形
     *
     * Parameters:
     * geometry - {<SuperMap.Geometry>}  这里传进来的是矩形
     * style    - {Object}  用户设置的style
     * featureId - {String}  当前绘制的feature的id
     */
    drawRectangle:function(geometry, style, featureId){
        //绘制矩形我们可以将其创建为一个矩形的面环,就可以重复利用以前的代码
        var geo = (new SuperMap.Geometry.LinearRing([
            new SuperMap.Geometry.Point(geometry.x, geometry.y),
            new SuperMap.Geometry.Point(geometry.x+geometry.width, geometry.y),
            new SuperMap.Geometry.Point(geometry.x+geometry.width, geometry.y+geometry.height),
            new SuperMap.Geometry.Point(geometry.x, geometry.y+geometry.height)
        ]));

        this.drawLinearRing(geo, style, featureId);
    },
    /**
     * Method: drawText
     * This method is only called by the renderer itself.
     *
     * Parameters:
     * location - {<SuperMap.Point>}
     * style    - {Object}
     */
    drawText: function(location, style) {
        style = SuperMap.Util.extend({
            fontColor: "#000000",
            labelAlign: "cm"
        }, style);
        var pt = this.getLocalXY(location);

        if (style.labelXOffset  || style.labelYOffset ) {
            var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset;
            var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset;
            pt[0] += xOffset;
            pt[1] -= yOffset;
        }

        this.setCanvasStyle("reset");
        this.canvas.fillStyle = style.fontColor;
        this.canvas.globalAlpha = style.fontOpacity || 1.0;
        var fontStyle = [style.fontStyle ? style.fontStyle : "normal",
                         "normal", // "font-variant" not supported
                         style.fontWeight ? style.fontWeight : "normal",
                         style.fontSize ? style.fontSize : "1em",
                         style.fontFamily ? style.fontFamily : "sans-serif"].join(" ");
        var labelRows = style.label.split('\n');
        var numRows = labelRows.length;
        if (this.canvas.fillText) {
            // HTML5
            this.canvas.font = fontStyle;
            this.canvas.textAlign =
                SuperMap.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
                "center";
            this.canvas.textBaseline =
                SuperMap.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[1]] ||
                "middle";
            var vfactor =
                SuperMap.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
            if (vfactor == null) {
                vfactor = -.5;
            }
            var lineHeight =
                this.canvas.measureText('Mg').height ||
                this.canvas.measureText('xx').width;
            pt[1] += lineHeight*vfactor*(numRows-1);
            for (var i = 0; i < numRows; i++) {
                if(style.rotation != 0)
                {
                    this.canvas.save();
                    this.canvas.translate(pt[0], pt[1]);
                    this.canvas.rotate(style.rotation*Math.PI/180);
                    this.canvas.fillText(labelRows[i], 0,  (lineHeight*i));
                    this.canvas.restore();
                }else{
                    this.canvas.fillText(labelRows[i], pt[0], pt[1] + (lineHeight*i));
                }
            }
        } else if (this.canvas.mozDrawText) {
            // Mozilla pre-Gecko1.9.1 (<FF3.1)
            this.canvas.mozTextStyle = fontStyle;
            // No built-in text alignment, so we measure and adjust the position
            var hfactor =
                SuperMap.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[0]];
            if (hfactor == null) {
                hfactor = -.5;
            }
            var vfactor =
                SuperMap.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
            if (vfactor == null) {
                vfactor = -.5;
            }
            var lineHeight = this.canvas.mozMeasureText('xx');
            pt[1] += lineHeight*(1 + (vfactor*numRows));
            for (var i = 0; i < numRows; i++) {
                var x = pt[0] + (hfactor*this.canvas.mozMeasureText(labelRows[i]));
                var y = pt[1] + (i*lineHeight);
                this.canvas.translate(x, y);
                this.canvas.mozDrawText(labelRows[i]);
                this.canvas.translate(-x, -y);
            }
        }
        this.setCanvasStyle("reset");
    },

    /**
     * Method: dashStyle
     *
     * Parameters:
     * style - {Object}
     * widthFactor - {Number}
     *
     * Returns:
     * {String} A Canvas Parameters of setLineDash Method 'strokeDasharray' value
     */
    dashStyle: function(style, widthFactor) {
        if(!style)return [];
        var w = style.strokeWidth * widthFactor;
        var str = style.strokeDashstyle;
        switch (str) {
            case 'solid':
                return [];
            case 'dot':
                return [1, 4 * w];
            case 'dash':
                return [4 * w, 4 * w];
            case 'dashdot':
                return [4 * w, 4 * w, 1, 4 * w];
            case 'longdash':
                return [8 * w, 4 * w];
            case 'longdashdot':
                return [8 * w, 4 * w, 1, 4 * w];
            default:
                if(!str)return [];
                if(SuperMap.Util.isArray(str))return str;
                str=SuperMap.String.trim(str).replace(/\s+/g, ",");
                return str.replace(/\[|\]/gi,"").split(",");
        }
    },

    /**
     * Method: getLocalXY
     * transform geographic xy into pixel xy
     *
     * Parameters:
     * point - {<SuperMap.Geometry.Point>}
     */
    getLocalXY: function(point) {
        var resolution = this.getResolution();
        var extent = this.extent;
        var x = (point.x / resolution + (-extent.left / resolution));
        var y = ((extent.top / resolution) - point.y / resolution);
        return [x, y];
    },

    /**
     * Method: clear
     * Clear all vectors from the renderer.
     */
    clear: function() {
        var height = this.root.height;
        var width = this.root.width;
        this.canvas.clearRect(0, 0, width, height);
        this.features = {};
        if (this.hitDetection) {
            this.hitContext.clearRect(0, 0, width, height);
        }
    },

    /**
     * Method: getFeatureIdFromEvent
     * Returns a feature id from an event on the renderer.
     *
     * Parameters:
     * evt - {<SuperMap.Event>}
     *
     * Returns:
     * {<SuperMap.Feature.Vector} A feature or null.  This method returns a
     *     feature instead of a feature id to avoid an unnecessary lookup on the
     *     layer.
     */
    getFeatureIdFromEvent: function(evt) {
        var feature = null;
        if (this.hitDetection) {
            // this dragging check should go in the feature handler
            if (!this.map.dragging) {
                var xy = evt.xy;
                var x = xy.x | 0;
                var y = xy.y | 0;
                var data = this.hitContext.getImageData(x, y, 1, 1).data;
                if (data[3] === 255) { // antialiased
                    var id = data[2] + (256 * (data[1] + (256 * data[0])));
                    if (id) {
                        feature = this.features["SuperMap.Feature.Vector_" + (id - 1 + this.hitOverflow)][0];
                    }
                }
            }
        }
        return feature;
    },

    /**
     * Method: eraseFeatures
     * This is called by the layer to erase features; removes the feature from
     *     the list, then redraws the layer.
     *
     * Parameters:
     * features - {Array(<SuperMap.Feature.Vector>)}
     */
    eraseFeatures: function(features) {
        if(!(SuperMap.Util.isArray(features))) {
            features = [features];
        }
        for(var i=0; i<features.length; ++i) {
            delete this.features[features[i].id];
        }
        this.redraw();
    },

    CLASS_NAME: "SuperMap.Renderer.AnimatorCanvas"
});

redpajama_set_name: RedPajamaGithub


text:
Reconnecting Communities in Kosovo society
Challenging Kosovo's corrupt higher education system
Strengthening Kosovo's ties to the EU and regional cooperation
Open calls and forms
Civic Energy Centers
Apply to Kosovo Research and Analysis Fellowship
Open Society Foundations mark 30th anniversary of presence in Kosovo
The Kosovo Foundation for Open Society, founded in September 1999, is part of the Open Society Foundations - a network of philanthropic foundation and the biggest private donor of independent groups working for justice, democratic governance and human rights.
Deadline extended: Applications for KRAF open until October 20
Call for applications for the Kosovo Research and Analysis Fellowship
Kosovo Foundation for Open Society awards grants and implements its own projects with the aim of building an inclusive society and a vibrant democracy in Kosovo. At the moment, the Foundation's work focuses on four main programmatic themes.
To advocate for change, the Foundation has established a number of branded platforms that foster open dialogue and highlight original research and good public policies.
Thirty Years: Open Society and its Friends
Subscribe to receive updates about KFOS
ProOpen coalition
Coalition for Integrity and Transparency in the University - KITU

redpajama_set_name: RedPajamaCommonCrawl


The Fountain of Neptune that has been standing in the Długi Targ for 350 years and the Crane are the most widely recognized symbols of Gdańsk where people queue every day to take photos. Abraham van den Blocke was the author of the fountain's design and stonework while the Neptune was supposed to refer to Gdańsk links to the sea. The Fountain was cast in 1615 at the initiative of the then city mayor, Bartłomiej Schachmann. The fountain was saved from destruction during the WWII because its individual fragments were hidden out of Gdańsk. In 1954, the Fountain of Neptune returned to Długi Targ to the delight of tourists and inhabitants of Gdańsk. A Gdańsk legend about the famous Goldwasser tincture related to the fountain is worth mentioning here. According to that legend, Neptune was so outraged because of the coins thrown into the fountain that he hit the water with his trident breaking the coins into tiny gold flakes that, as of that time, adorned the herbal tincture with their glitter. Goldwasser has been one of the most popular liquors in the nearby restaurants to this day.


text:
/* See LICENSE for licensing information */

#ifndef TOR_BITARRAY_H
#define TOR_BITARRAY_H

/**
 * \file bitarray.h
 *
 * \brief Implements a variable-sized (but non-resizeable) bit-array.
 **/

#include "orconfig.h"
#include <string.h>
#include "lib/cc/torint.h"
#include "lib/malloc/malloc.h"

#if SIZEOF_INT == 4
#define BITARRAY_SHIFT 5
#elif SIZEOF_INT == 8
#define BITARRAY_SHIFT 6
#else
#error "int is neither 4 nor 8 bytes. I can't deal with that."
#endif /* SIZEOF_INT == 4 || ... */
#define BITARRAY_MASK ((1u<<BITARRAY_SHIFT)-1)

/** A random-access array of one-bit-wide elements. */
typedef unsigned int bitarray_t;
/** Create a new bit array that can hold n_bits bits. */
static inline bitarray_t *
bitarray_init_zero(unsigned int n_bits)
{
  /* round up to the next int. */
  size_t sz = (n_bits+BITARRAY_MASK) >> BITARRAY_SHIFT;
  return tor_calloc(sz, sizeof(unsigned int));
}
/** Expand ba from holding n_bits_old to n_bits_new,
 * clearing all new bits.  Returns a possibly changed pointer to the
 * bitarray. */
static inline bitarray_t *
bitarray_expand(bitarray_t *ba,
                unsigned int n_bits_old, unsigned int n_bits_new)
{
  size_t sz_old = (n_bits_old+BITARRAY_MASK) >> BITARRAY_SHIFT;
  size_t sz_new = (n_bits_new+BITARRAY_MASK) >> BITARRAY_SHIFT;
  char *ptr;
  if (sz_new <= sz_old)
    return ba;
  ptr = tor_reallocarray(ba, sz_new, sizeof(unsigned int));
  /* This memset does nothing to the older excess bytes.  But they were
   * already set to 0 by bitarry_init_zero. */
  memset(ptr+sz_old*sizeof(unsigned int), 0,
         (sz_new-sz_old)*sizeof(unsigned int));
  return (bitarray_t*) ptr;
}
/** Free the bit array ba. */
static inline void
bitarray_free_(bitarray_t *ba)
{
  tor_free(ba);
}
#define bitarray_free(ba) FREE_AND_NULL(bitarray_t, bitarray_free_, (ba))

/** Set the bitth bit in b to 1. */
static inline void
bitarray_set(bitarray_t *b, int bit)
{
  b[bit >> BITARRAY_SHIFT] |= (1u << (bit & BITARRAY_MASK));
}
/** Set the bitth bit in b to 0. */
static inline void
bitarray_clear(bitarray_t *b, int bit)
{
  b[bit >> BITARRAY_SHIFT] &= ~ (1u << (bit & BITARRAY_MASK));
}
/** Return true iff bitth bit in b is nonzero.  NOTE: does
 * not necessarily return 1 on true. */
static inline unsigned int
bitarray_is_set(bitarray_t *b, int bit)
{
  return b[bit >> BITARRAY_SHIFT] & (1u << (bit & BITARRAY_MASK));
}

#endif /* !defined(TOR_BITARRAY_H) */


text:
Shortlisting of candidates for the Independent Communications Authority of South Africa (ICASA) Council
This premium content has been made freely available
The Committee as well as its Subcommittee met in camera to shortlist candidates to be interviewed to fill vacancies at the Independent Communications Authority of South Africa (ICASA) Council.
The report below is a summary of the decisions made and is taken from the Annoucement, Tablings and Committees (ATC).
Progress report of the Portfolio Committee on Communications on the filling of five vacancies on the Independent Communications Authority of South Africa (ICASA), dated 5 May 2015
The Portfolio Committee on Communications, having considered two letters from the Minister of Communications:-
informing the Assembly that the terms of office of Independent Communications Authority of South Africa (Icasa) councillors Mr William Currie and Mr Joseph Lebooa will expire on 30 September 2014 and the terms of office of Mr William Stucke and Ms Ntombizodwa Ndhlovu on 31 October 2014; and
informing the Assembly that she accepted the resignation of Dr Marcia Socikwa, a councillor of the Independent Communications Authority of South Africa (Icasa), with effect from 31 January 2015.
The Committee advertised the call for nominations of persons to serve on the ICASA Council. 85 nominations were received and considered by the Committee.
On 17 March 2015, the Committee adopted the following names as the subcommittee to shortlist and appoint: (1) Hon JC Moloi-Moropa (Chairperson and ANC member); (2) Hon G Davis (DA); (3) Hon M Ndlozi (EFF); (4) Hon D Tsotetsi (ANC); (5) Hon M Tseli (ANC), (6) Hon M Kekana (ANC), and (7) Hon W Madisha (COPE).
On 5 May 2015, the subcommittee recommended to the Portfolio Committee that the following 12 candidates be shortlisted: (i) Mr Diratsagae Maoke; (ii) Mr Keabetswe Modimoeng; (iii) Mr Lance Rothschild; (iv) Mr Lumko Mtimde; (v) Mr Paris Mashile; (vi) Mr Peter Hlapolosa; (vii) Mr Peter Zimri; (viii) Mr Sello Molefe; (ix) Mr Thamsanqa Ntenteni; (x) Mr Yengwayo Kutta; (xi) Ms Botlenyana Mokhele; and (xii) Ms Dimakatso Qocha.
The interviews are scheduled to take place on Tuesday, 26 May 2015.
Report for information purposes.
No related
We don't have attendance info for this committee meeting


text:
Mount Coot-tha in 1929
This description and history of Mt Coot-tha, Brisbane, is taken from the Brisbane Courier, 1 July 1929:
'MOUNT COOT-THA: ITS HISTORY REVIEWED
ASSET TO BRISBANE.
Mount Coot-tha has been appropriately termed the "Mount of Beauty." All who have stood on its crest have been impressed with the grandeur of the panorama that it gives of city, river, bay, and mountain ranges. Contrasting with the natural scenery. Mount Coot-tha itself Is beautiful. Even in the long-gone days, When the place that was to be Brisbane was an unbroken vista of trees; when nothing but virgin bush was to be seen from the eminence where thousands have since looked on to and beyond the city; when smoke from aboriginal fires was the only intrusion in the picture of nature. Mount Coot-tha must have presented a wildly beautiful scene.
Mt Coot-tha, 1910. (Queensland Historical Atlas)
ORIGIN OF NAME.
Mount Coot-tha was once a favourite ground of the blacks, who hunted marsupials and birds, and very often found hives of native honey there. From such discoveries the mountain owes the origin of its present name. "Coot-tha," in the native dialect, meant "dark native honey." This meaning is applied to the word in "Tom Petrie's Reminiscences," where the author gives its pronunciation as "Ku-ta"; other translations interpret it merely as "honey." The name by which the mount was known to early settlers, and by which it Is popularly called to-day, was "One Tree Hill." This was derived from the fact that an arboreal monarch once stood on the summit in solitary splendour. In the early '40's a dense forest of large trees grew on the top of the hill, and with their thinning-out, the giant tree became more conspicuous year by year because of its isolation and great size. So the ridge became "One Tree Hill." The big tree was killed by careless picnickers lighting fires at its base. Frequent blazes scorched its trunk, and sapped its life, and one day the stark old tree had to be felled.
HISTORY OF THE RESERVE.
The attractions of "One Tree Hill" as a recreation and picnic ground were recognised from the days when civilisation began to penetrate the country around Moreton Bay. Many of the first residents found it a delightful retreat. But the mountain's timbers were exploited for some years before Mount Coot-tha was definitely made a park reserve. According to the Assistant Under Secretary for Public Lands (Mr. C.W. Holland), the land was originally set apart under the Crown Lands Alienation Act of 1868 as a reserve for timber for railway purposes. A survey was made in 1874, and the reserve was found to contain an area of 1476 acres. In 1880 the reservation for timber for railway purposes was cancelled, and an area of 1500 acres was permanently reserved for a public park. A deed of grant upon trust, "for the appropriation thereof as a public park for the recreation, convenience, health, and amusement of the inhabitants of the city of Brisbane in our said colony, and for no other use and purpose whatsoever," was issued to Sir Charles Lilley, Sir A.H. Palmer, Sir Samuel Griffith, and the Hon. H. E. King.
APPOINTMENT OF TRUSTEES.
Subsequent changes' placed the following gentlemen on the trust, in succession: - Sir Thos. Mcilwraith, Mr. John Stevenson, Sir Hugh Nelson, Sir Alfred Cowley, Hon. Albert Norton, Mr. E.H. Macartney (Queensland's Agent General-elect), Dr. E.S. Jackson, and Sir Robert Philp. Mr. H. W. Radford, Clerk of the Legislative Council, acted as hon. secretary to the board of trustees, and took a keen interest in the reserve. Afterwards Messrs. C. W. Costin and C. R. Gregory, each in turn Clerk of the Legislative Council, acted as hon. secretary. Grants were made by the Government to the trustees for roads, erection of shelter shed, fencing, salary of caretaker, &c. The name was changed from "One Tree Hill" to "Mount Coot-tha." by notice published in the "Government Gazette" of August 10, 1883. In 1919 the trustees surrendered their trust in favour of the Brisbane City Council, which was then appointed as trustee.
AREA OF THE RESERVE.
With the growth of the city Mount Coot-tha reserve also has expanded. The area has grown, by additions from time to time, to a little over 2567 acres. The Brisbane City Council is about to apply to the Land Administration Board for the grant of an additional area of 35 acres of Crown land, formerly held as a quarantine reserve, in the direction of Indooroopilly, and, if it is obtained, the acquisition of another 10 acres of privately-owned land that lies between will make the total area of the Mount Coot-tha reserve 2612 acres. But the reserve has grown in other respects. Roads have been improved, a fine new kiosk at the peak of the hill has replaced the rustic structure that formerly stood there, and a pretty look-out tower has been built. Indicative of the number of vehicles that now run to the city's favourite observation point is the fact that one-way traffic is about to be introduced between the Summit and Simpson's road, Paddington.
HISTORIC FIG TREES.
One of the Moreton Bay fig trees that stand at the top of Mount Coot-tha was planted by King George V. (then Prince George), and his brother, the late Duke of Clarence (then Prince Albert), during their visit to Brisbane in 1882. On that occasion the late Sir Thomas Mcllwraith and Earl Clanwilliam also planted trees. To-day the historic fig trees spread a kindly shade for visitors, and add to the quiet beauty of the surroundings. Mount Coot-tha's altitude of 746ft., and its proximity to the city - it is about four miles from the General Post Office - makes it a very valuable asset to "Brisbane. But other features - the Summit and the Devil's Slide, the broken dams, and the stony gullies, where water used to run, the cool shady slopes, and the bubbling streams, the stately trees, and pretty shrubs, have endeared the whole reserve to those who love to commune with Nature. In all parts of Australia, and, Indeed, abroad, there are people who are glad' that, owing to the prevision of public-minded men. "Brisbane has Its Mount Coat-tha."
Labels: Brisbane, Mt Coot-tha
Christopher Dawson, MPHA (Qld). Public Historian. BA (Anthropology), Grad. Dip (Applied History), University of Queensland. dawsonchris@outlook.com 0432 554470 (9am - 4pm, Mon-Fri)
What Would Win in a Fight Between a Tiger and a Bull?
Brisbane's Lost Plague Cemetery
A Brisbane River Shark Attack, 1847
The Seven Devils of George Street
The Gallows of the Old Windmill Tower
Fatal Shark Attacks in Ross Creek, Townsville
The Lost Graveyard of Terranora Creek
Aboriginal history (8) Beaudesert (1) biography (5) Boggo Road (17) Bribie Island (1) Brisbane (24) Caboolture River (1) Camooweal (1) cemeteries (6) Chinese immigration (1) Cleveland (1) Cloncurry (1) convicts (2) Cooktown (1) crime (1) crocodiles (1) cryptozoology (9) ethics (14) fakelore (11) floods (2) German Station (4) ghosts (17) Gold Coast (3) hangings (16) Inala (1) Ipswich (2) Kippa Ring (1) Logan (2) Lowood (1) Mackay (1) Moreton Island (2) Mt Coot-tha (1) music (1) Normanton (1) Nundah (3) paranormal industry (15) Pine River (1) plague (1) sharks (8) South Brisbane Cemetery (9) South Sea Islanders (2) Thargomindah (1) Tigers (2) Toorbal (1) Toowoomba (1) tours (2) Townsville (1) War (1) whales (4) Wolston Park (1) womens' history (4) Woogaroo (1) Yeppoon (1)
Should Ghost-Hunting Be Banned?
© and website design: Christopher Dawson MPHA (Qld). Powered by Blogger.


text:
Digital Strategy Data Analytics & AI
Deep Learning With NVIDIA DGX-1
We highlight some practical considerations for the Deep Learning practitioner relevant to neural network training on the NVIDIA DGX-1.
Michael Catalano
The NVIDIA DGX-1 is a state-of-the-art integrated system for deep learning and AI development. In this white paper, you will learn the best practices for dramatic acceleration of deep learning algorithms over CPU-based hardware. This includes how the DGX-1 can bring efficiencies to training on batch size, input image size and model complexity.
The NVIDIA DGX-1 is a state of the art integrated system for deep learning and AI development. Making use of 8 interconnected NVIDIA Tesla V100 GPUs, the DGX-1 offers dramatic acceleration of deep learning algorithms over CPU-based hardware. In this paper, we highlight a few best practices that enable the DGX-1 end-user to fully capitalize on its industry-leading performance. Benchmark testing was conducted with a common GPU workload, convolutional neural network (CNN) training, using the Keras deep learning API. We first examined the dependence of training efficiency on 3 factors: batch size, input image size and model complexity. Next, the scalability of training speed was assessed using a multi-tower, data-parallel approach. Finally, we demonstrate the importance of learning rate scaling when employing multiple GPU workers.
Business justification
Graphics processing units (GPUs) are driving the present Artificial Intelligence (AI) boom. The value of GPUs in the AI domain lies in their ability to compute complex mathematical operations orders of magnitude faster than CPUs. Deep Neural Networks (DNNs) are a class of algorithms to which the processing prowess of GPUs is particularly well suited. Ever since a DNN and GPU were used to win the ImageNet Large Scale Visual Recognition Challenge (ILSVRC) in 20121, the field of machine learning has seen an explosion of new DNN variants aimed at solving the field's most exciting and difficult problems, e.g., driverless vehicles and human-machine dialog. Given the growing complexity of these algorithms and the ever-increasing size of the unstructured data required to run them, GPUs have become the cost of entry for AI development.
The NVIDIA DGX-1 is the first off-the-shelf hardware/software stack specifically designed for DNN workloads. The unit is composed of 8 topologically optimized NVIDIA Tesla V100 GPUs with 16 GB of RAM each, 2 Intel Xeon E5-2698 v4 CPUs, 512 MB system RAM, 4×1.92 TB of SSD storage and 4×100 Gb/s Infiniband network adapters and occupies about as much space as three compute nodes in a server rack. All of this hardware comes pre-loaded with NVIDIA's software stack, including a Deep Learning SDK that optimizes DL computations for single- and multi-GPU processing. Additionally, the device supports all major DL programming frameworks through NVIDIA-Docker, a GPU-enabled version of the popular Docker containerization engine. The DGX-1 is preconfigured as such to get the end-user up and running in as little time as possible and with maximum flexibility.
While the DGX-1 is the current industry leader in Deep Learning, NVIDIA has been boosting the performance of its GPU devices over the last few years at a staggering pace. Compared to the K80 GPU Accelerator, which was the state of the art in 2014, a single Tesla V100 offers a nearly 7X speedup. In fact, a recent hardware and software upgrade for the DGX-1 netted a 3X single-GPUspeedup compared to the original configuration.2Used in concert, the 8 GPUs of the DGX- 1 provide enough compute power to handle the most intensive of Deep Learning workloads. However, this industry-leading performance is largely dependent on the specific routines and parameters that are defined by the end-user. Herein, we describe some points of consideration for getting the most out of a DGX-1 installation.
CNN models
Three convolutional neural network (CNNs) architectures were used for comparative performance analysis: ResNet-503, VGG164, and a shallow network similar to LeNet-5.5 The shallow network, termed LittleCNN, was composed of 2 convolutional blocks of 32 and 64 output volume depth, a fully connected layer of 512 neurons, and a final 10-node softmax layer. Each convolutional block consisted of two successive 3×3 convolutions followed by 2×2 max pooling. The ReLU activation function was used for all layers and dropout regularization was applied after the first and second convolutional blocks and after the fully connected layer at drop rates of 0.25, 0.25, and 0.5, respectively. All models were trained using RMSprop optimization with a learning rate of 1×10-4, unless otherwise noted.
The CNN models were trained on the CIFAR-10 dataset as well as synthetic image data. CIFAR-10 consists of 60,000 32×32 color images of 10 different classes and is split into 50,000 training and 10,000 validation images.6 CIFAR-10 images were normalized to values between 0 and 1 prior to forward pass through the network. A synthetic image set was constructed by generating 1,024 224×224 arrays of values randomly sampled from a uniform distribution over [0,1]. A corresponding label set was created by randomly assigning each synthetic image a one-hot encoded integer value of 0 through 9. Synthetic images were used to emulate the larger image sizes, e.g., ImageNet, that are typically fed to deep CNNs like ResNet and VGG variants. Because these images were randomly generated, they were used to evaluate GPU performance purely on computational speed without regard to classification accuracy.
All experiments were conducted on a NVIDIA DGX-1 (8xTesla V100) running Ubuntu 16.04.3. Model training was carried out using Keras 2.1.5 with TensorFlow 1.7.0 as the backend. A GPU-enabled Keras Docker file was obtained from GitHub7, which included TensorFlow and CUDA/cuDNN dependencies.
Multi-GPU Training
Multi-GPU training was performed using the multi_gpu_model() function included with Keras, which accommodates multi-tower, data-parallel training. The data-parallel approach is outlined as follows:
Instantiate CNN model on CPU, where the CPU represents the "parameter server."
Instantiate a copy of the model on n devices (GPUs). Copied model instances are also referred to as towers.
During training, split the input batch into n equal-sized minibatches, where n is the number of GPUs or towers.
Compute backward pass on each tower simultaneously to obtain gradients for each model copy.
Once gradients have been computed for all towers, concatenate gradients on CPU, update model weights accordingly and pass new weights from the parameter server to the GPU devices.
Repeat steps 3-6.
Data parallelism allows a higher throughput of images during training, and therefore reduced training time. It should be noted that the above implementation is an example of synchronous training, where the parameter server (CPU) must wait for all towers (GPUs) to complete backpropagation before updating the model weights. Consequently, a truly linear scaleup of training speed is theoretically impossible. Currently, Keras only supports synchronous training with a multi- tower approach. The effect of synchronicity on multi-GPU training efficiency is discussed further in the Results section of this paper.
Benchmarking experiments
The following investigations were carried out using a single-GPU training scheme.
a) Batch Size. The effect of input image batch size on image throughput, GPU utilization and validation accuracy was evaluated by training Little CNN on CIFAR-10 images for 30 epochs with a varying batch size of 16, 32, 64, 128, 256, 512, 1024, and 2048images.
b) Image size. Experiment a) was repeated (up to a batch size of 256) using large 224×224 synthetic images to examine the effect of input image dimensions on image throughput and GPU
c) Model size. Large CNNs ResNet-50 and VGG16 were trained for30 epochs with synthetic images (224×224) using a batch size of GPU utilization was monitored and compared to the results of experiment b) to assess the effect of neural network complexity on training efficiency.
The multi-GPU training scheme described above was implemented for the following.
d) Multi-GPU acceleration. The relationship between image throughput and number ofGPUs used was investigated by training LittleCNN, ResNet-50 and VGG16 on synthetic images (batch size = 32) using 1, 2, 4 and 8 of the available GPUs in theDGX-1.
e) Learning rate scaling. LittleCNN was trained on CIFAR-10 for 30 epochs using 1, 2, 4, and 8GPUs with and without adjustment of the learning rate according to the number of GPU workers (batch size = 128 images per GPU). A constant learning rate of 1 x 10-4 was used for all tests, then the tests were repeated using a learning rate of 1 x 10-4 ∙ N, where N is the number of GPU workers. Time-dependent validation accuracy was monitored for all tests to evaluate the effect of learning rate scaling on model convergence.
The processing efficiency of a GPU during deep neural network training is dependent on several factors. We first examined the effect of batch size on training speed. Batch size refers to the number of input samples, images in this case, that are fed through the network during each weight update step. In addition to training speed, batch size can have a marked effect on the learning capacity of a neural network. If the input batch is too small, the gradients calculated during each training step can be so noisy that the model has difficulty learning anything at all. If it is too large, convergence tends to be slow, and one runs the risk of converging on a local minimum instead of pushing to the globally optimal solution. Assuming a total image set size of at least a couple thousand images, a batch size of 32 to 128 images typically provides a good compromise between these two extremes.
The LittleCNN network was trained on CIFAR-10 for 30 epochs using a single GPU with an input batch size ranging from 16 to 2048 images. The combination of the shallow network architecture of LittleCNN and the small size of the CIFAR-10 images results in substantially decreased computational demand compared to a modern CNN workflow. Consequently, a very large input batch size is needed to maximize training throughput (Figure 1). We observed that image throughput only began to level off at around 26,000 images per second once a batch size of 1024 images was used. Using a batch size of 16 images, the network was only able to process 3,000 images per second. This discrepancy illustrates a phenomenon known as GPU starvation, where the GPU spends some amount of time in an idle state during training due to a bottleneck in the input pipeline. In this case, because the images are small, the GPU computation time required to process a small batch of images is less than that of overhead processes. For example, a batch of input images must be copied from the CPU to the GPU device during each training iteration. When the batch size is small, the task of copying a batch to GPU becomes an input bottleneck, which results in underutilization of the GPU. This effect is reflected in the average GPU utilization curve shown in Figure 1.
Figure 1. Dependence of image throughput, average GPU utilization and validation accuracy on batch size for little CNN trained on CIFAR-10 using a single GPU. Validation accuracy represents model classification performance after training for 30 epochs.
Batch size also has a marked effect on the convergence rate of the model. Figure 1 shows the validation accuracy on the test image set after 30 training epochs. Using a batch size of 2048, the final accuracy is only 50%, while a batch size of only 16 images achieves an accuracy of 76%.
This occurs because a larger batch size affords fewer weight updates during a training epoch, and therefore, the model learns more slowly. In practice, large batch sizes on the order of 1×103 are typically not used unless a distributed training approach is implemented. This approach is discussed in the context of multi-GPU data parallelism later in this paper.
We have demonstrated that underutilization of the GPU during training can be corrected by increasing the batch size, which increases the number of computations the GPU must complete during each training step. Another way to raise the computational demand on the GPU is to increase the size of the input images. To examine the effect of image size on throughput and GPU efficiency, we repeated the experiment above using much larger 224×224-pixel synthetic images (49X the pixel count of CIFAR-10). Figure 2 shows the training speed in images per second and GPU utilization for LittleCNN trained on the large synthetic images using batch sizes of 16 to 256 images, above which the GPU memory became a constraint.
As anticipated, training velocities using synthetic images are significantly lower than those observed for the CIFAR-10 experiments. We can expect image throughput with larger images to be lower, since the time it takes to process an image will always be greater regardless of GPU optimization.
More notably, however, is the much lesser degree to which the batch size affects throughput. Using large images, the training speed is near its maximum with a batch size of 64 images, compared to 1,024 when the model was trained on CIFAR-10. Furthermore the relative speedup between 64 and 16 images per batch was only 1.3X when using synthetic images, while the corresponding rate increase for CIFAR-10 was 2.8X.
Additionally, GPU utilization had no apparent dependence on batch size and was always above 70% when the model was trained with synthetic images, indicating that the GPU was sufficiently fed in all cases.
Figure 2. Dependence of image throughput and average GPU utilization on batch size for LittleCNN trained on 224×224 synthetic images for 30 epochs using a single GPU.
In addition to batch size and input image size, the architecture of the deep neural network itself is directly related to computational demand, and therefore, image throughput. The size of a CNN is typically described in terms of depth, where depth refers to the number of convolutional plus terminal fully-connected layers in the network. Since the introduction of AlexNet, CNNs have grown deeper to yield incrementally greater classification accuracy on diverse datasets, such as ImageNet.
For comparison, AlexNet (2012) contains 7 layers and VGG16 (2014) contains 16. The greater depth of modern CNNs as well as larger image sizes used for classification problems have made GPUs indispensable, as training these networks on CPU is prohibitively slow. In order to assess the performance of the DGX-1 on a more modern workload, we repeated the previous experiment using 2 additional moderately deep CNNs: ResNet-50 and VGG16. Image throughput and GPU utilization for LittleCNN, ResNet-50 and VGG16 are illustrated in Figure 3.
Each network was trained on a single GPU for 30 epochs with a batch size of 32, as a 64-image batch caused the GPU to run out of memory when training the larger ResNet-50 and VGG16 models. As expected, the training speeds of the larger networks are less than that of LittleCNN (442 images/second). We found the average image throughput for ResNet-50 and VGG16 to be 192 and 156 images per second, which is consistent with the DGX-1 benchmarking results published on the TensorFlow website: 195 and 144 images per second, respectively, using a single GPU, synthetic 224×224 images and a batch size of 32.8 Interestingly, the 3 networks are also differentiable on the basis of GPU utilization. LittleCNN was the least efficient, with an average GPU usage of 82%, while ResNet-50 and VGG16 showed average utilizations of 88 and 92%, respectively. Thus, there appears to be a correlation between network complexity and GPU efficiency. While this makes sense intuitively, the exact nature of this relationship is unclear. Since we are operating well outside the regime of GPU starvation, one might expect GPU utilization to approach the theoretical maximum regardless of the specific network architecture, but empirically, this is not the case. The authors of TensorFlow suggest a full utilization criterion of 80-100%, within which input pipeline bottlenecks can effectively be ruled out.9 This rather large window suggests an inherent variability in GPU utilization during training. Therefore, we can infer that all 3 models achieved full utilization despite their relative differences in measured GPU usage.
Figure 3. Time-dependent GPU utilization for LittleCNN, ResNet-50 and VGG16 network architectures trained for 30 epochs on 224×224 synthetic images with batch size of 32.
Multi-GPU Acceleration
We next investigated training speed enhancement using a multi-GPU approach on the DGX-1. Multi-GPU training implies using 2 or more of the GPU devices in parallel with the aim of increasing image throughput and consequently reducing the time required to achieve a desired level of classification accuracy. The most common form of GPU parallelization is called data parallelism, which is detailed in the Experimental Setup and Methodology section of this paper. The utility of this approach has been described recently by researchers at Facebook, who were able to achieve state-of-the-art accuracy on the ImageNet dataset in 1 hour using 256 GPUs in parallel.10 Given that training the same model on the same dataset using 8 equivalent GPUs required 29 hours, Facebook's large-scale results represent a near linear scaleup in terms of training speed. We attempted to replicate these findings at much smaller scale using 1-8 GPUs on the DGX-1.
We trained the 3 models discussed previously on synthetic images using 1-8 GPUs with a batch size of 32 x G, where G is the number of GPU devices used for training. Figure 4 shows the relative training speed enhancement with respect to image throughput as a function of GPUs. Doubling the number of GPUs from 1 to 2 provided respectable enhancement for the VGG16 model, with a speedup factor of 1.74, while ResNet-50 showed only 1.29x improvement. However, a significant discrepancy relative to linear scaling became apparent when more than 2 GPUs were used. With 4 GPU towers, the maximum scale factor achieved in our experiments was 2.46 with VGG16, while both ResNet-50 and LittleCNN showed speedup factors less than 50% of linear. Furthermore, scaling from 4 to 8 GPUs offered only marginal improvement for ResNet-50 and actually showed a decrease in throughput for the LittleCNN and VGG16 networks.
Figure 4. Image throughput speedup with multi-GPU training relative to a single GPU. All experiments were conducted using 224×224 synthetic images with batch size of 32 images per GPU. TensorFlow benchmark results were generated with the ResNet-50 network architecture.
Our multi-GPU benchmarking results highlight a limitation in distributed CNN training caused by synchronous stochastic gradient descent (SGD). In synchronous SGD, model gradients are computed locally on each GPU device then copied to the parameter server where the gradients are averaged to obtain the new global updated weights. The updated model parameters are then copied to each GPU worker to be used for the next round of forward passes. While this method of gradient averaging over a split input batch has been shown to be roughly equivalent to single-GPU training on the whole batch in terms of model accuracy, it has some drawbacks with respect to training speed. Most notably, there is an inherent penalty on GPU efficiency when training is conducted in a synchronous fashion. Because computation of the new model parameters is dependent on the results from all GPU towers, all GPUs are required to complete training on their respective input batches before a model update can be implemented. Consequently, the time required to complete a single model update is constrained by the slowest GPU worker. This effect becomes more apparent as more GPUs are added to the training job, since total GPU idle time will be proportional to the total number of GPUs. Furthermore, network bottlenecks between the parameter server and GPU workers can exacerbate this inefficiency. While network constraints should not apply to the DGX-1, which is self-contained, they must be considered when training is distributed across multiple servers.
The drawbacks of synchronous GPU training can be overcome by using an asynchronous SGD (ASGD) training scheme. In ASGD, the global model parameters stored on the parameter server are updated by each GPU worker independently as soon as local gradient computation has completed. This eliminates the overhead time penalty associated with synchronous updates. ASGD can be implemented with native TensorFlow primitives to achieve near linear multi-GPU scaleup. A benchmark performance summary published by TensorFlow using asynchronous multi-GPU training on the DGX-1 is given in Figure 4. ASGD has clear benefits in terms of training throughput and scalability in multi-GPU environments, but it is not without its own drawbacks. Because the global parameters are modified each time a GPU worker completes a training iteration regardless of that individual worker's progress relative to others, the shared model can potentially be reverted to a less optimized state by a single lagging GPU. This is known as the "stale gradient" problem. Stale gradients can slow convergence to a globally optimal solution, which means that asynchronous training tends to require more epochs of training to achieve a given level of accuracy compared to a synchronous implementation.
Learning Rate Scaling
In addition to computational efficiency, one must consider the impact on training effectiveness when employing a multi-GPU architecture. Training throughput is only part of the picture. It is equally important to examine how quickly a model learns when scaleup is implemented. As described previously in the context of single-GPU training, hyperparameters like batch size can have a substantial effect on model convergence. In the case of multi-GPU training, it has been demonstrated that adjustment of the learning rate is important to achieve optimal performance. 10Learning rate is a user-specified value that determines the magnitude by which model weights are adjusted during each training step. Its effect on the updated model weights can be described by
∆Wt= α∙∇L(y, F(x; Wt ))
where ∆Wt is the change in model weights for iteration t, α is the learning rate, and the right-most expression describes the computed loss gradient as a function of the current model weights. Thus, increasing the learning rate increases the magnitude of change in the model weights. The learning rate must be high enough such that training does not converge in a local optimum, but low enough that the global optimum solution is not passed over.
It has been shown that linear scaling of the learning rate with respect to the number of GPU workers improves the accuracy of multi-GPU implementations. To test this, we conducted 30 epochs of training using CIFAR-10 and LittleCNN on 1, 2, 4 and 8 GPUs with unscaled and scaled learning rates. Figure 5 shows the time-dependent cross-entropy loss on the validation images using a constant learning rate of 1×10-4 for each GPU configuration. There is a clear incremental performance loss as the number of GPUs is increased. However, when the learning rates were scaled to account for the number of GPUs, validation losses were roughly equivalent after 30 training epochs (Figure 6). This effect can be explained on the basis of relative batch size. Because the number of images sent to each GPU during training is fixed at 128, increasing the number of GPU workers effectively increases the total batch size for each training step. Consequently, a single training epoch with 8 GPUs includes 8x fewer weight updates compared to one epoch using a single GPU. This same phenomenon was observed while examining the effects of batch size on training speed and accuracy using 1 GPU (Figure 1): larger batch sizes imply fewer learning opportunities per epoch. As illustrated in Figure 6, this effect can be reversed simply by increasing the magnitude of learning at each of those less frequent update steps, i.e., increasing the learning rate.
Figure 5. Time-dependent cross-entropy loss for multiple-GPU training of LittleCNN on CIFAR-10 with a batch size of 128 images per GPU and a constant learning rate of 1×10-4.
Figure 6. Time-dependent cross-entropy loss for multiple-GPU training of LittleCNN on CIFAR-10 with a batch size of 128 images per GPU and scaled learning rates of 1×10-4, 2×10-4, 4×10-4, 8×10-4 for 1,2,4 and 8 GPUs, respectively.
We have highlighted some practical considerations for the Deep Learning practitioner relevant to neural network training on the NVIDIA DGX-1. Benchmarking experiments showed that GPU performance is related to 3 dimensions: 1) the size of the input batch, 2) the size of the input images, and 3) the size of the neural network architecture. We demonstrated that the enhanced processing power of GPUs can only be fully realized if the computational demand on the GPU during training exceeds all other overhead processes, such as the I/O pipeline. All 3 of the factors listed above are proportional to the number of computations that must be performed on the GPU during training, and thus, directly impact GPU utilization. Large CNN models such as ResNet-50 and VGG variants with input images of at least 224×224 pixels show complete utilization with as few as 32 images per batch. However, shallower models, such as those used to solve much smaller image sets like CIFAR-10 and MNIST (32×32 and 28×28 pixels, respectively), will exhibit GPU starvation at moderate batch sizes. In our testing, a simple 3-layer CNN trained on CIFAR-10 achieved maximum utilization only when a batch size of at least 2048 images was used.
We also examined the scalability of distributed training using a data-parallel approach with Keras and TensorFlow. We found that while implementation with Keras provided reasonable scaleup using 2 GPUs compared to 1, increasing the number of GPUs beyond 2 showed sharply diminishing returns. This is a well-documented phenomenon that occurs during synchronous training with multiple GPU workers. Currently, Keras only supports synchronous SGD training. However, near linear scale-up can be accomplished using low-level TensorFlow primitives for asynchronous SGD, as established by TensorFlow's published benchmarks on the DGX-1. Finally, we demonstrated the importance of learning rate adjustment when using multiple GPUs. It is recommended to increase the learning rate by a factor of N for a data-parallel training scheme, where N is equal to the number of GPUs. We found that this linear scaling criterion rectified depressed model convergence rates caused by multi-GPU training.
Krizhevsky, A., Sutskever, I., Hinton, G. Imagenet classification with deep convolutional neural networks. Advances in Neural Information Processing Systems 2012 1: 1097-1105.
https://developer.nvidia.com/cudnn.
He, K., Zhang, X., Ren, S., Sun, J. Deep residual learning for image recognition 2015.
Simonyan, K., Zisserman, A. Very deep convolutional networks for large-scale image recognition. ICLR conference paper 2015.
LeCun, Y., Bottou, L., Bengio, Y., Haffner, P. Gradien-based learning applied to document recognition. Proc. Of the IEEE 1998.
https://www.cs.toronto.edu/~kriz/cifar.html.
https://github.com/keras-team/keras/tree/master/docker.
https://www.tensorflow.org/performance/benchmarks.
https://www.tensorflow.org/performance/performance_guide.
Goyal, P., Dollar, P., Girshick, R., Noordhuis, P., Wesolowski, L., Kyrola, A., Tullock, A., Jia, Y., He, K. Accurate, large minibatch SGD: training ImageNet in 1 hour 2017.
Download this white paper.


This is a guide to installing TightVNC on the Raspberry Pi. The latest version of Raspbian now includes the RealVNC which is an alternative remote desktop tool. If you would prefer to use RealVNC then it is easily enabled through the raspi-config tool, this guide will be useful for those wanting to understand about creating other applications to run on start-up through systemd.
The command line is a great way to manage a remote Linux computer if you don't mind typing in commands, but sometimes you need to be able to view a GUI. Short of a very long cable one of the best ways of doing this is using the VNC protocol (Virtual Network Computing).
The TightVNC server creates a different virtual window which is replicated on a different machine using the TightVNC client. This differs from RealVNC which normally usually echos the default screen.
When most people think of a remote desktop tool then they usually think of the way that RealVNC operates. In this case when you move the mouse, or launch an application it displays the same output on a physically attached screen (normally HDMI) and on a remote client over the network.
TightVNC is useful if you want to run something different to what is on the main screen. For example you could use it so that you can have a status display on the main screen (via HDMI) with a different display used for the configuration which is displayed on a laptop.
Another advantage of TightVNC is that it is FREE open source software. As such it has no restrictions on use and you can view and modify the source code. Real VNC is a commercial version of VNC and needs to be licensed. Real VNC is free to use for personal use on the Raspberry Pi, but does have restrictions if you want to do other things with it which may require a commercial license. This includes running the Raspbian Desktop image on a PC as that does not included the commercial software.
The following are instructions on how to configure this on the Raspberry Pi (Debian based distribution). It should be similar for any other Debian based distribution (eg. Ubuntu) which uses Systemd start-up files. Some other distributions use different software install tools and possibly different init setup tools.
says that it's virtual desktop 1. You would connect to this using :1 at the end of the IP address in the client, this is the one we will setup to start automatically later.
You may need to change the user name on line 8. It is currently set to pi which is the default username on the Raspberry Pi Raspbian image. Set this to whatever username you want to login to TightVNC as. Do not put root as the user.
TightVNC will now start at startup. I'd recommend you reboot at this point to make sure it's working but youc an just stop and start the service using the systemctl command instead.
Note that ssvnc - which is the version I installed on my Kubuntu computer is based on tightvnc, but also has the ability to configure the ssh tunnelling (see later) through the GUI.
TightVNC allows anyone that knows the password to login as the user that it's running under. It also does this without encryption so anyone can snoop in on the session. Fortunately there is a secure solution, which is to tunnel the connection over an ssh (secure shell) login.
The instructions above are sufficient for the Raspberry Pi running Raspbian (Debian). On other systems you may need to make a further configuration change to start your normal window manager / application launcher. If you get a blank screen when connecting edit the file ~/.vnc/xstartup and replace the line /etc/X11/Xsession with one of the following as appropriate.