{"id":1081,"date":"2023-05-18T10:39:44","date_gmt":"2023-05-18T10:39:44","guid":{"rendered":"https:\/\/users.aber.ac.uk\/rkj\/site\/?page_id=1081"},"modified":"2023-06-20T08:32:29","modified_gmt":"2023-06-20T08:32:29","slug":"visualizer","status":"publish","type":"page","link":"https:\/\/users.aber.ac.uk\/rkj\/site\/research\/visualizer\/","title":{"rendered":"Visualizer"},"content":{"rendered":"\n<p>Visualize the fuzzy lower and upper approximations below. (Uses code from <a href=\"https:\/\/martin-thoma.com\/html5\/clustering\/clustering.htm\">here<\/a>)<\/p> \n        <table>\n    <colgroup>\n       <col span=\"1\" style=\"width: 15%;\">\n       <col span=\"1\" style=\"width: 25%;\">\n       <col span=\"1\" style=\"width: 15%;\">\n       <col span=\"1\" style=\"width: 25%;\">\n       <col span=\"1\" style=\"width: 20%;\">\n    <\/colgroup>\n            <tr>\n                <td><span class=\"hint\" title=\"Choose type of approximation\" >type<\/span><\/td>\n                <td>\n                    <select id=\"approx\" onchange=\"updateBoard()\">\n                        <option>standard<\/option>\n                        <option>VQRS<\/option>\n                        <option>OWA<\/option>\n                    <\/select>    \n                <\/td>\n                <td>relation<\/td>\n                <td>\n                    <select id=\"similarity\" onchange=\"updateBoard()\">\n                        <option>similarity1<\/option>\n                        <option>similarity1softm<\/option>\n                        <option>similarity2<\/option>\n                        <option>similarity2softm<\/option>\n                        <option>similarity3<\/option>\n                        <option>similarity3softm<\/option>\n                    <\/select>                \n                <\/td>\n                <td>\n                    <select id=\"operators\" onchange=\"updateBoard()\">\n                        <option>KD<\/option>\n                        <option>Lukasiewicz<\/option>\n                        <option>Algebraic<\/option>\n                    <\/select>\n                <\/td>\n            <\/tr>\n            <tr>\n                <td><span class=\"hint\" title=\"The next point you insert will have class number...\">class<\/span><\/td>\n                <td><input type=\"number\" step=\"1\" value=\"1\" id=\"class\" min=\"1\" max=\"4\" onchange=\"updateNumberBgColor()\"\/><\/td>\n                <td><span class=\"hint\" title=\"Parameter (standard deviation) for similarity relations 2 and 3\">parameter<\/span><\/td>\n                <td><input type=\"number\" value=\"100\" min=\"1\" step=\"10\" id=\"parameter\" onchange=\"updateBoard()\"\/><\/td>\n                \n                <td><input type=\"button\" title=\"Generate normally-distributed random data\" value=\"Generate\" onclick=\"generateRandomData()\"><\/td>\n                <td><input type=\"button\" title=\"Reset the points if they have been marked by LAIR and\/or deleted\" value=\"Reset\" onclick=\"resetPoints(1)\"><\/td>\n            <\/tr>\n            <tr>\n                <td><span class=\"hint\" title=\"Sample the space and calculate the lower or upper approximation membership for each sample\">map<\/span><\/td>\n                <td>\n                    <select id=\"map\" onchange=\"updateBoard()\">\n                        <option>none<\/option>\n                        <option>lower<\/option>\n                        <option>upper<\/option>\n                    <\/select> \n                <td><span class=\"hint\" title=\"How much points will be spread for lower map. USE 1 WITH CAUTION! The bigger the value, the quicker the computation.\">spread<\/span><\/td>\n                <td><input type=\"number\" step=\"1\" value=\"10\" id=\"density\" min=\"1\" onchange=\"updateBoard()\"\/><\/td>\n                <td> <select id=\"algorithm\" onchange=\"setAlgorithm()\">\n                        <option>LAIR<\/option>\n                        <option>LAIR2<\/option>\n                        <option>NoiseRemove<\/option>\n                        <option>Oversample1<\/option>\n                        <option>Oversample2<\/option>\n                        <option>OversampleN<\/option>\n                        <option>SMOTE<\/option>\n                    <\/select> \n                <\/td>\n<td><input type=\"button\" title=\"Run algorithm\" value=\"RUN\" onclick=\"runAlgorithm()\"><\/td>\n            <\/tr>\n            <tr>\n                <td>alphaL<\/td>\n                <td><input type=\"number\" value=\"0.2\" step=\"0.1\" min=\"0\" max=\"1\" id=\"alphal\" onchange=\"updateBoard()\"\/><\/td>\n                <td>betaL<\/td>\n                <td><input type=\"number\" value=\"1.0\" step=\"0.1\" min=\"0\" max=\"1\" id=\"betal\" onchange=\"updateBoard()\"\/><\/td>\n                <td><input type=\"button\" title=\"Delete all points marked for deletion\" value=\"Delete\" onclick=\"deletePoints()\"><\/td>\n                <td><\/td>\n            <\/tr>\n            <tr>\n                <td>alphaU<\/td>\n                <td><input type=\"number\" value=\"0.1\" step=\"0.1\" min=\"0\" max=\"1\" id=\"alphau\" onchange=\"updateBoard()\"\/><\/td>\n                <td>betaU<\/td>\n                <td><input type=\"number\" value=\"0.6\" step=\"0.1\" min=\"0\" max=\"1\" id=\"betau\" onchange=\"updateBoard()\"\/><\/td>\n                <td><input type=\"button\" title=\"Clear the data\" value=\"Clear\" onclick=\"clearBoard()\"><\/td>\n                <td><a target=\"_blank\" onclick=\"drawVisualization(1)\" rel=\"noopener\"> \n                    <input type=\"button\" title=\"3D view of the lower approximation\" value=\"3D Lower\" style=\"font-size:60%\"><\/a><br \/>\n                    <a target=\"_blank\" onclick=\"drawVisualization(2)\" rel=\"noopener\"> \n                    <input type=\"button\" title=\"3D view of the upper approximation\" value=\"3D Upper\" style=\"font-size:60%\"><\/a>\n                <\/td>\n            <\/tr>\n        <\/table>\n        <input type=\"button\" title=\"Download screenshot\" value=\"Screenshot\" onclick=\"screen()\" style=\"font-size:60%\"> <br \/>\n        <canvas id = \"myCanvas\"\n                width = \"530\" height = \"600\"\n                style = \"border:1px solid #000;\"\n        > <\/canvas>\n\n<script type=\"text\/javascript\" src=\"https:\/\/visjs.github.io\/vis-graph3d\/standalone\/umd\/vis-graph3d.min.js\">\n<!--\n\n\/\/-->\n<\/script>\n\n    <script type=\"text\/javascript\">\n<!--\n\n    var data = null;\n    var data2 = null;\n    var graph = null;\n    var graph2 = null;\n\n    \/\/ Called when the Visualization API is loaded.\n    function drawVisualization(choice) {\n       var myWindow = window.open(\"\", \"VizWindow\", \"width=560,height=600\");\n       myWindow.document.write(\"<html><body><div id = \\\"mygraph\\\"><\/div><div id = \\\"mygraph2\\\"><\/div><\/body><\/html>\");\n\n      \/\/ Create and populate a data table.\n      data = new vis.DataSet(); \n      var currentClass = parseInt(document.getElementById(\"class\").value);\n      var add = parseInt(document.getElementById(\"density\").value);\n\n      var counter=0;\n\n      for (var x=0; x < canvas.width; x+=add) {\n          for (var y=0; y < canvas.height; y+=add) {\n                var approx = approximations(x,y,currentClass);\n                var measure = approx.lower;\n\n                if (choice==2) measure = approx.upper;\n\n                \/\/if (document.getElementById(\"map\").value==\"lower\") measure = approx.lower;\n                \/\/else if (document.getElementById(\"map\").value==\"upper\") measure = approx.upper;\n                \n                data.add({id:counter++,x:x,y:canvas.height-y,z:measure});\n                \n          }\n      }\n\n      data2 = new vis.DataSet(); currentClass = parseInt(document.getElementById(\"class\").value);\n      for (var i = 0; i < points.length; i++) {\n           var approx = approximations(points[i][\"x\"],points[i][\"y\"],currentClass);\n           var measure = approx.lower;\n\n           if (choice==2) measure = approx.upper;\n           data2.add({id:i,x:points[i][\"x\"],y:canvas.height-points[i][\"y\"],z:measure});\n      }\n\n      \/\/ specify options\n      var options = {\n        width:  '530px',\n        height: '600px',\n        style: 'surface',\n        zLabel: 'membership',\n        showPerspective: true,\n        showGrid: true,\n        showShadow: false,\n        keepAspectRatio: true,\n        verticalRatio: 0.5,\n        cameraPosition: {\n          horizontal: -0.34,\n          vertical: 0.6,\n          distance: 1.6\n        }\n      };\n\n      var options2 = {\n        width:  '530px',\n        height: '600px',\n        style: 'dot',\n        dotSizeRatio: 0.01,\n        zLabel: 'membership',\n        showPerspective: false,\n        showGrid: true,\n        keepAspectRatio: true,\n        legendLabel:'value',\n        verticalRatio: 1.0,\n        cameraPosition: {\n          horizontal: -0.54,\n          vertical: 0.5,\n          distance: 1.6\n        }\n      }\n\n      \/\/ Instantiate the graph object.\n      var container = myWindow.document.getElementById(\"mygraph\");\n      var canv = myWindow.opener.document.getElementById(\"myCanvas\");\n\n      options.colormap = function (x) {\n         \/\/return getColor(currentClass,approx.lower);\n         return { r: 255 * x, g: 255- 255*x - 0, b: 0, a: (1 + x) \/ 2 };\n      };\n\n      graph = new vis.Graph3d(container, data, options);\n\n      var container2 = myWindow.document.getElementById(\"mygraph2\");\n      graph2 = new vis.Graph3d(container2, data2, options2);\n\n      \/\/ needed to send the mouse events from the child window to the parent\n      myWindow.document.addEventListener(\"mousemove\", function(evt) {\n         var oe = evt;\n         canv.dispatchEvent(new oe.constructor(oe.type, oe));\n      });\n      \/*myWindow.document.addEventListener(\"mousedown\", function(evt) {\n         var oe = evt;\n         canv.dispatchEvent(new oe.constructor(oe.type, oe));\n      });*\/\n      myWindow.document.addEventListener(\"drag\", function(evt) {\n         var oe = evt;\n         canv.dispatchEvent(new oe.constructor(oe.type, oe));\n      });\n      myWindow.document.addEventListener(\"touchmove\", function(evt) {\n         var oe = evt;\n         canv.dispatchEvent(new oe.constructor(oe.type, oe));\n      });\n    }\n\nvar algorithmChoice = \"LAIR\";\n\nfunction setAlgorithm() {\n    algorithmChoice = document.getElementById(\"algorithm\").value;\n}\n\n\/\/ how much to match the number of instances in the majority class\nvar percentageInstances = 100;\n\nfunction runAlgorithm() {\n    switch (algorithmChoice) {\n        case \"LAIR\": LAIR(1); break;\n        case \"LAIR2\": LAIR(2); break;\n        case \"NoiseRemove\": LAIR3(); break;\n        case \"Oversample1\": copyData(); oversample(percentageInstances); break; \/\/ generate random points within feature range, use lowest lower approx as a threshold for acceptance of points\n        case \"Oversample2\": copyData(); oversample(percentageInstances,\"average\"); break; \/\/ same as Oversample1 but use average lower approx\n        case \"OversampleN\": copyData(); oversampleNeighbors(percentageInstances); break;\n        case \"SMOTE\": copyData(); SMOTE(percentageInstances); break;\n    }\n}\n\n\/\/ make a copy of the points\nfunction copyData() {\n    prevPoints = JSON.parse(JSON.stringify(points));\n}\n\n\nfunction setCursorByID(id,cursorStyle) {\n    var elem;\n    if (document.getElementById &&\n    (elem=document.getElementById(id)) ) {\n        if (elem.style) elem.style.cursor=cursorStyle;\n    }\n}\n\n\n\nfunction euclideanDist(p1, p2) {\n    return Math.sqrt(\n                  Math.pow(p1[\"x\"]-p2[\"x\"], 2)\n                + Math.pow(p1[\"y\"]-p2[\"y\"], 2));\n}\n\nfunction getNrOfClusters() {\n        return maxKnearestNeighborClasses;\n}\n\nfunction getColor(i, transparency) {\n    return getColorGeneral(i, getNrOfClusters(), transparency);\n}\n\nfunction getColorGeneral(i, k, transparency) {\n    var t = (i+1)*(360\/k);\n    var color = 'hsla('+t+', 75%, 50%, '+transparency+')';\n    return color;\n}\n\n\n\n\/** Returns a dictionary with a cluster and a radius*\/\nfunction getKNearestNeighbor(k, mouseCoords) {\n    if (points.length == 0) {\n        return {\"cluster\":0, \"radius\":INITIAL_RADIUS};\n    }\n\n    var Distances = new Array();\n    for(var i = 0; i < points.length; i++) {\n        points[i][\"cluster\"] = points[i][\"class\"];\n        var dist = euclideanDist(points[i], mouseCoords);\n        Distances.push({\"dist\":dist, \"class\":points[i][\"class\"]});\n    }\n\n    numberSort = function (a,b) {\n        return a[\"dist\"] - b[\"dist\"];\n    };\n\n    Distances.sort(numberSort);\n\n    if (Distances.length < k) {\n        \/* initialise array to count *\/\n        var classCount = new Array(maxKnearestNeighborClasses);\n\n        for (var i=0;i<maxKnearestNeighborClasses;i++) {\n            classCount[i] = 0;\n        }\n\n        \/* count classes *\/\n        for (var i = 0; i < Distances.length; i++) {\n            classCount[Distances[i][\"class\"]]++;\n        }\n\n        \/* find maximum *\/\n        maxClass = 0;\n        maxClassCount = 0;\n        for (var i =0; i <maxKnearestNeighborClasses;i++) {\n            if(maxClassCount < classCount[i]) {\n                maxClassCount = classCount[i];\n                maxClass = i;\n            }\n        }\n        return {\"cluster\":maxClass, \"radius\":Distances.slice(-1)[0][\"dist\"]};\n    } else {\n        \/* initialise array to count *\/\n        var classCount = new Array(maxKnearestNeighborClasses);\n\n        for (var i=0;i<maxKnearestNeighborClasses;i++) {\n            classCount[i] = 0;\n        }\n\n\n        for (var i = 0; i < k; i++) {\n            classCount[Distances[i][\"class\"]]++;\n        }\n\n        \/* find maximum *\/\n        maxClass = 0;\n        maxClassCount = 0;\n        for (var i =0; i <maxKnearestNeighborClasses;i++) {\n            if(maxClassCount < classCount[i]) {\n                maxClassCount = classCount[i];\n                maxClass = i;\n            }\n        }\n        return {\"cluster\":maxClass,\n                \"radius\":Distances[k-1][\"dist\"]};\n    }\n}\n\nvar cwidth = 660;\nvar cheight = 600;\n\nfunction drawBoard(canvas, mouseCoords, radius) {\n    var context = canvas.getContext('2d');\n    var k = parseInt(kElement.value);\n    context.canvas.width  = cwidth; \/\/window.innerWidth - 50;\n    context.canvas.height = cheight; \/\/window.innerHeight - 120;\n    context.clearRect(0, 0, canvas.width, canvas.height);\n\n    var currentClass = parseInt(document.getElementById(\"class\").value);\n    var algorithm = getKNearestNeighbor;\n\n    alpha_l = parseFloat(document.getElementById(\"alphal\").value);\n    beta_l = parseFloat(document.getElementById(\"betal\").value);\n    alpha_u = parseFloat(document.getElementById(\"alphau\").value);\n    beta_u = parseFloat(document.getElementById(\"betau\").value);\n\n    if (document.getElementById(\"map\").value!=\"none\") {\n        var add = parseInt(document.getElementById(\"density\").value);\n        for (x=0; x < canvas.width; x+=add) {\n            for (y=0; y < canvas.height; y+=add) {\n                var approx = approximations(x,y,currentClass);\n                \n                if (document.getElementById(\"map\").value==\"lower\") context.fillStyle = getColor(currentClass,approx.lower);\n                else if (document.getElementById(\"map\").value==\"upper\") context.fillStyle = getColor(currentClass,approx.upper);\n                context.fillRect(x, y, add\/2, add\/2);\n\n                \/\/var algorithmResult = algorithm(k, {\"x\":x, \"y\":y});\n                \/\/context.fillStyle = getColor(algorithmResult[\"cluster\"],1);\n                \/\/context.fillRect(x, y, add\/2, add\/2);\n            }\n        }\n    }\n\n    drawPoints(canvas, k);\n\n    var approx = approximations(mouseCoords[\"x\"], mouseCoords[\"y\"],currentClass);\n \n    context.fillStyle = \"rgba(255, 255, 255, 0.7)\";\n    context.fillRect(0,0,canvas.width,20);\n\n    context.fillStyle = \"black\";\n    context.fillRect(0,20,canvas.width,1);\n    context.fillText(\"Class: \"+currentClass, 10, 12);\n    context.fillText(\"Lower approx: \"+approx.lower, 70, 12);\n    context.fillText(\"Upper approx: \"+approx.upper, 260, 12);\n\n    \/\/ visualise the weights\n    \/*if (document.getElementById(\"approx\").value == \"OWA\") {\n        context.fillText(\"Lower weights: \", 10, 65);\n        for (var i = 0; i < weights.length; i++) {\n             context.fillStyle = \"black\";\n             context.fillRect(i+10,130,1,-100*weights[i]);\n\n        }\n\n        context.fillText(\"Upper weights: \", 10, 165);\n        for (var i = 0; i < weights2.length; i++) {\n             context.fillStyle = \"black\";\n             context.fillRect(i+10,230,1,-200*weights2[i]);\n\n        }\n    }*\/\n}\n\nfunction approximations(x,y,currentClass) {\n    \/\/currentClass=-1;\n    if (document.getElementById(\"approx\").value == \"OWA\") return approximationsOWA(x,y,currentClass);\n    else if (document.getElementById(\"approx\").value == \"VQRS\") return approximationsVQRS(x,y,currentClass);\n    else return approximationsStandard(x,y,currentClass);\n}\n\n\n\n\/\/ Calculate the lower and upper approximations for the point (x,y)\nfunction approximationsStandard(x,y,currentClass) {\n    var lower = 1.0; var upper = 0.0;\n\n    for (var i = 0; i < points.length; i++) {\n           if (points[i][\"cluster\"]==-1) continue; \/\/ this instance has been flagged to be ignored\n\n           var belongsToClass = 0;\n           if (points[i][\"class\"]==currentClass) belongsToClass=1;\n\n           if (x!=points[i][\"x\"]&&y!=points[i][\"y\"]) \nlower = Math.min(lower, implicator(similarity(x,y,points[i][\"x\"],points[i][\"y\"]),belongsToClass));\n           if (lower==0) break;\n    }\n\n    for (var i = 0; i < points.length; i++) {\n           if (points[i][\"cluster\"]==-1) continue; \/\/ this has been flagged to be ignored\n\n           var belongsToClass = 0;\n           if (points[i][\"class\"]==currentClass) belongsToClass=1;\n\n           if (x!=points[i][\"x\"]&&y!=points[i][\"y\"]) \nupper = Math.max(upper, tnorm(similarity(x,y,points[i][\"x\"],points[i][\"y\"]),belongsToClass));\n           if (upper==1) break;\n    }\n    \n    \/\/ this also includes the mouse coordinates\n    \/\/lower = Math.min(lower, implicator(similarity(x,y,x,y),1));\n    \/\/upper = Math.max(upper, tnorm(similarity(x,y,x,y),1));\n\n     return {\"lower\": lower,\n                \"upper\": upper, \"currentClass\":currentClass};\n}\n\n\nfunction approximationsVQRS(x,y,currentClass) {\n    var val= 0.0; var denom = 0.0;\n\n    for (var i = 0; i < points.length; i++) {\n           if (points[i][\"cluster\"]==-1) continue; \/\/ this has been flagged to be ignored\n\n           var belongsToClass = 0;\n           if (points[i][\"class\"]==currentClass) belongsToClass=1;\n\n           var sim = similarity(x,y,points[i][\"x\"],points[i][\"y\"]);\n           val += tnorm(sim,belongsToClass);\n           denom += sim;\n    }\n    \n    \/\/ this also includes the mouse coordinates\n    \/\/val += tnorm(similarity(x,y,x,y),1);\n    \/\/denom += similarity(x,y,x,y);\n\n    var value; \n    if (denom==0) value=0;\n    else value = val\/denom;\n\n    lower = Q(value, alpha_l, beta_l);\n    upper = Q(value, alpha_u, beta_u);\n\n     return {\"lower\": lower,\n                \"upper\": upper, \"currentClass\":currentClass};\n}\n\nfunction Q(x, alpha, beta) {\n    var ret = 0;\n    var denomVal = (beta - alpha) * (beta - alpha);\n\n    if (x <= alpha)\n        ret = 0;\n    else if (x < ((alpha + beta) \/ 2.0)) {\n        ret = (2.0 * (x - alpha) * (x - alpha)) \/ denomVal;\n    } else if (x < beta) {\n        ret = 1 - ((2.0 * (x - beta) * (x - beta)) \/ denomVal);\n    } else if (beta <= x) {\n        ret = 1;\n    } else {ret=10000;}\n\n    return ret;\n}\n\n\n\/\/ OWA-based fuzzy rough lower and upper approximations\n\/\/ The inf and sup operators are replaced with more relaxed versions using OWA\n\/\/ This uses a min PriorityQueue: smallest element first\nfunction approximationsOWA(x,y,currentClass) {\n    var implications = new PriorityQueue();\n    var tnorms = new PriorityQueue();\n    var num_implications = 0;\n    var num_tnorms = 0;\n\n    for (var i = 0; i < points.length; i++) {\n           if (points[i][\"cluster\"]==-1) continue; \/\/ this has been flagged to be ignored\n\n           var belongsToClass = 0;\n           if (points[i][\"class\"]==currentClass) belongsToClass=1;\n\n           var sim = similarity(x,y,points[i][\"x\"],points[i][\"y\"]);\n\n\t   implications.add(implicator(sim,belongsToClass));\n           tnorms.add(tnorm(sim,belongsToClass));\n\n           num_implications++;\n           num_tnorms++;\n\t   \n    }\n\n    \/\/ this also includes the mouse coordinates\n    \/*implications.add(implicator(similarity(x,y,x,y),1));\n    tnorms.add(tnorm(similarity(x,y,x,y),1));\n    num_implications++;\n    num_tnorms++;*\/\n\n    var sum_lower = 0;\n    var w_i;\n\n    for (w_i=0;w_i<num_implications;w_i++) {\n        var implication = implications.remove();\n        var w = weight(num_implications-w_i,alpha_l,beta_l,num_implications);\n\tsum_lower += implication*w;\n        weights[w_i] = w;\n    }\n\n    var sum_upper = 0;\n    w_i=0;\n\n    for(w_i=0;w_i<num_tnorms;w_i++) {\n\tvar tnorm1 = tnorms.remove();\n        var w = weight(num_tnorms-w_i,alpha_u,beta_u,num_tnorms);\n\tsum_upper += tnorm1*w;\n        weights2[w_i]=w;\n        \/\/console.log((w_i+1)+\": \"+w_max(w_i+1,num_tnorms)+\"  \"+w);\n        \/\/sum_upper += tnorm1*w_max(w_i+1,num_tnorms);\n    }\n\n     return {\"lower\": sum_lower,\n                \"upper\": sum_upper, \"currentClass\":currentClass};\n}\n\nvar weights = new Array();\nvar weights2 = new Array();\n\n\/\/ alternative weightings \nfunction w_min(i,p) {\n    return (p-i+1)\/(p*(p+1)\/2.0);\n}\n\nfunction w_max(i,p) {\n    return i\/(p*(p+1)\/2.0);\n}\n\n\/\/ weights based on fuzzy quantification\nfunction weight(i, a, b, n) {\n    \/*if (a==0.2) { \/\/ lower\n       if ((n-i)==0) return 1;\n       else return 0;\n    }  \n    else { \/\/ upper\n       if (i==1) return 1;\n       else return 0;\n    }*\/\n    var w = Q(i\/n,a,b)-Q((i-1)\/n,a,b);\n    \/\/if (a==0.1) console.log((n-i)+\": \"+w);\n    return w;\n}\n\nfunction similarity(x1,y1,x2,y2) {\n    if (document.getElementById(\"similarity\").value == \"similarity1\") return similarity1(x1,y1,x2,y2);\n    else if (document.getElementById(\"similarity\").value == \"similarity1softm\") return similarity1softmin(x1,y1,x2,y2);\n    else if (document.getElementById(\"similarity\").value == \"similarity2\") return similarity2(x1,y1,x2,y2);\n    else if (document.getElementById(\"similarity\").value == \"similarity2softm\") return similarity2softmin(x1,y1,x2,y2);\n    else if (document.getElementById(\"similarity\").value == \"similarity3softm\") return similarity3softmin(x1,y1,x2,y2);\n    else return similarity3(x1,y1,x2,y2);\n}\n\n\nfunction similarity1(x1,y1,x2,y2) {\n     return tnorm(Math.max(0,1 - Math.abs(x1 - x2)\/cwidth),\n                  Math.max(0,1 - Math.abs(y1 - y2)\/cheight));\n}\n\n\/\/ instead of using a tnorm, use softmin\nfunction similarity1softmin(x1,y1,x2,y2) {\n     var sim1 = Math.max(0,1 - Math.abs(x1 - x2)\/cwidth);\n     var sim2 = Math.max(0,1 - Math.abs(y1 - y2)\/cheight);\n     var denom = Math.exp(-sim1) + Math.exp(-sim2);\n\n     \/\/console.log(sim1 + \"    \"+sim2);\n     \/\/console.log((Math.exp(-sim1)\/denom) + \" \"+(Math.exp(-sim2)\/denom)+\"\\n\\n\");\n\n     return sim1*(Math.exp(-sim1)\/denom) + sim2*(Math.exp(-sim2)\/denom);\n}\n\n\n\nfunction similarity2(x1,y1,x2,y2) {\n   var std = parseFloat(document.getElementById(\"parameter\").value);\n   var calc = Math.exp(-((x1-x2)*(x1-x2)\/(2*std*std)));\n   if (calc==0) return 0;\n   else return tnorm(calc,Math.exp(-((y1-y2)*(y1-y2)\/(2*std*std))));\n}\n\nfunction similarity2softmin(x1,y1,x2,y2) {\n   var std = parseFloat(document.getElementById(\"parameter\").value);\n   var sim1 = Math.exp(-((x1-x2)*(x1-x2)\/(2*std*std)));\n   var sim2 = Math.exp(-((y1-y2)*(y1-y2)\/(2*std*std)));\n\n   var denom = Math.exp(-sim1) + Math.exp(-sim2);\n   return sim1*(Math.exp(-sim1)\/denom) + sim2*(Math.exp(-sim2)\/denom);\n}\n\nfunction similarity3(x1,y1,x2,y2) {\n   var std = parseFloat(document.getElementById(\"parameter\").value);\n   var firstterm = (x2- (x1- std)) \/ (x1- (x2- std));\n   var secondterm = ((x1+ std) - x2) \/ ((x1 + std) - x1);\n   var res1 = Math.max( Math.min(firstterm, secondterm), 0);\n\n   firstterm = (y2- (y1- std)) \/ (y1- (y2- std));\n   secondterm = ((y1+ std) - y2) \/ ((y1 + std) - y1);\n   var res2 = Math.max( Math.min(firstterm, secondterm), 0);\n\n   return tnorm(res1,res2);\n}\n\nfunction similarity3softmin(x1,y1,x2,y2) {\n   var std = parseFloat(document.getElementById(\"parameter\").value);\n   var firstterm = (x2- (x1- std)) \/ (x1- (x2- std));\n   var secondterm = ((x1+ std) - x2) \/ ((x1 + std) - x1);\n   var sim1 = Math.max( Math.min(firstterm, secondterm), 0);\n\n   firstterm = (y2- (y1- std)) \/ (y1- (y2- std));\n   secondterm = ((y1+ std) - y2) \/ ((y1 + std) - y1);\n   var sim2 = Math.max( Math.min(firstterm, secondterm), 0);\n\n   var denom = Math.exp(-sim1) + Math.exp(-sim2);\n   return sim1*(Math.exp(-sim1)\/denom) + sim2*(Math.exp(-sim2)\/denom);\n}\n\nfunction tnorm(a,b) {\n    if(document.getElementById(\"operators\").value == \"KD\") return tnormKD(a,b);\n    else if (document.getElementById(\"operators\").value == \"Algebraic\") return tnormAlg(a,b);\n    else return tnormL(a,b);\n}\n\nfunction implicator(a,b) {\n    if(document.getElementById(\"operators\").value == \"KD\") return implicatorKD(a,b);\n    else if (document.getElementById(\"operators\").value == \"Algebraic\") return implicatorAlg(a,b);\n    else return implicatorL(a,b);\n}\n\nfunction tnormKD(a,b) {\n  return Math.min(a,b);\n}\n\nfunction tnormL(a,b) {\n  return Math.max(a+b-1,0);\n}\n\nfunction tnormAlg(a,b) {\n  return a*b;\n}\n\nfunction implicatorKD(a,b) {\n  return Math.max(1 - a, b);\n}\n\nfunction implicatorL(a,b) {\n  return Math.min(1, 1 - a + b);\n}\n\nfunction implicatorAlg(a,b) {\n  return snormAlg(1-a,tnormAlg(a,b));\n}\n\nfunction snormAlg(a,b) {\n   return (a + b) - (a * b);\n}\n\n\/\/ Goguen implication\nfunction implicatorGog(a,b) {\n  if (a<=b) return 1;\n  else return Math.max(1,b\/a);\n}\n\nfunction clearBoard() {\n    points = new Array();\n    maxKnearestNeighborClasses = 1;\n    document.getElementById(\"map\").value = \"none\";\n    updateBoard();\n}\n\n\/** permanently add a point *\/\nfunction addPoint(event, canvas, mouseCoords, radius) {\n    updateBoard();\n\n    pointCluster = parseInt(document.getElementById(\"class\").value);\n\n    if (pointCluster > maxKnearestNeighborClasses) {\n        maxKnearestNeighborClasses = pointCluster;\n    }\n\n    var alreadyExists = false;\n    \/\/ check to see if this already exists, in which case it is deleted\n    for(var i = 0; i < points.length; i++) {\n        if (Math.sqrt((mouseCoords[\"x\"]-points[i][\"x\"])*(mouseCoords[\"x\"]-points[i][\"x\"]) + (mouseCoords[\"y\"]-points[i][\"y\"])*(mouseCoords[\"y\"]-points[i][\"y\"])) < POINT_RADIUS) {\n        \/\/if (mouseCoords[\"x\"]==points[i][\"x\"]&&mouseCoords[\"y\"]==points[i][\"y\"]) {\n             alreadyExists=true;\n             deleted.push(points[i]);\n             points.splice(i,1); \/\/ remove this point\n             break;\n        }\n    }\n\n    if (!alreadyExists) {\n        points.push({\"x\": mouseCoords[\"x\"],\n                 \"y\": mouseCoords[\"y\"],\n                 \"radius\": radius,\n                 \"cluster\": pointCluster,\n                 \"class\": pointCluster});\n    }\n\n    updateBoard();\n}\n\n\/** draw all permanently added points *\/\nfunction drawPoints(canvas, k) {\n    for (var i = 0; i < points.length; i++) {\n        context.beginPath();\n        context.arc(points[i][\"x\"],\n                    points[i][\"y\"],\n                    points[i][\"radius\"],\n                    0, 2 * Math.PI, false);\n        context.lineWidth = 1;\n        context.strokeStyle = 'black';\n        if (points[i][\"cluster\"]==-1) context.fillStyle=\"black\";\n        else context.fillStyle = getColor(points[i][\"cluster\"], getNrOfClusters(), 0.9);\n        context.fill();\n        context.stroke();\n    }\n}\n\nvar prevPoints = [];\n\n\/\/ used by LAIR\nfunction resetPoints(num) {\n    if (algorithmChoice == \"Oversample1\"||algorithmChoice == \"Oversample2\"||algorithmChoice == \"OversampleN\"||algorithmChoice == \"SMOTE\") {\n        points = prevPoints;\n        console.log(\"Restored previous data\");\n        updateBoard();\n    }\n    else {\n        console.log(\"Deleted: \"+deleted.length);\n        points = points.concat(deleted);\n        deleted = [];\n\n        for (var i = 0; i < points.length; i++) {\n           points[i][\"cluster\"]=points[i][\"class\"];\n        }\n\n        if (num==1) updateBoard();\n    }\n}\n\nvar deleted = [];\n\n\/\/ delete points that have been marked as removable by LAIR\/LAIR2\nfunction deletePoints() {\n    var newPoints = [];\n\n    for (var i = 0; i < points.length; i++) {\n       if (points[i][\"cluster\"]!=-1) newPoints.push(points[i]);\n       else deleted.push(points[i]);\n    }\n\n    points = newPoints;\n\n    updateBoard();\n}\n\n\/** get the current position of the mouse *\/\nfunction getMouseCoords(canvas, evt) {\n    var rect = canvas.getBoundingClientRect();\n    return {\n        \"x\": evt.clientX - rect.left - 1,\n        \"y\": evt.clientY - rect.top - 1\n    };\n}\n\nfunction getMouseCoords2(canvas, evt) {\n  var rect = canvas.getBoundingClientRect(), \/\/ abs. size of element\n    scaleX = canvas.width \/ rect.width,    \/\/ relationship bitmap vs. element for x\n    scaleY = canvas.height \/ rect.height;  \/\/ relationship bitmap vs. element for y\n\n  return {\n    \"x\": (evt.clientX - rect.left) * scaleX,   \/\/ scale mouse coordinates after they have\n    \"y\": (evt.clientY - rect.top) * scaleY     \/\/ been adjusted to be relative to element\n  }\n}\n\nfunction updateBoard() {\n    var canvas = document.getElementById(\"myCanvas\");\n    drawBoard(canvas, {\"x\":0,\"y\":0}, INITIAL_RADIUS);\n}\n\nfunction updateNumberBgColor(){\n\tvar domEl = document.getElementById(\"class\");\n\tif(parseInt(domEl.value) > getNrOfClusters()+1) {\n\t\tdomEl.value = getNrOfClusters()+1;\n\t}\n\tdomEl.style.backgroundColor = getColorGeneral(parseInt(domEl.value), Math.max(parseInt(domEl.value), getNrOfClusters()), 0.5);\n       updateBoard();\n}\n\n\/\/ Standard Normal variate using Box-Muller transform.\nfunction gaussianRandom(mean=0, stdev=1) {\n    let u = 1 - Math.random(); \/\/ Converting [0,1) to (0,1]\n    let v = Math.random();\n    let z = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v );\n    \/\/ Transform to the desired mean and standard deviation:\n    return z * stdev + mean;\n}\n\n\/\/ calculate the standard deviation for the data for use in the similarity relations that use it\n\/\/ not used by the code, but output to console for manual input\nfunction determineStDev() {\n    if (points.length>2) {\n        var stdev_x = 0; var mean_x = 0;\n        var stdev_y = 0; var mean_y = 0;\n \n        for (var i=0; i<points.length;i++) {\n            mean_x += points[i][\"x\"];\n            mean_y += points[i][\"y\"];\n        }\n\n        mean_x\/=points.length;\n        mean_y\/=points.length;\n\n        for (var i=0; i<points.length;i++) {\n            stdev_x += Math.pow(mean_x-points[i][\"x\"],2);\n            stdev_y += Math.pow(mean_y-points[i][\"y\"],2);\n        }\n        \n        stdev_x = Math.sqrt(stdev_x\/(points.length-1));\n        stdev_y = Math.sqrt(stdev_y\/(points.length-1));\n\n        console.log(\"Std dev of x: \"+stdev_x);\n        console.log(\"Std dev of y: \"+stdev_y+\"\\n\");\n    }\n    \n}\n\nfunction generateRandomData() {\n    points = new Array();\n    deleted = [];\n    maxKnearestNeighborClasses = 1;\n    var numPoints = 40; \/\/ number of points in a class\n    var numClasses = 2;\n    var mean_x = canvas.width\/4;\n    var mean_y = canvas.height\/4;\n    var classNum = 1;\n    var deviation = 50;\n\n    for(var i = 0; i < numPoints; i++) {\n      var x = Math.min(canvas.width,Math.max(0,Math.round(gaussianRandom(mean_x,deviation))));\n      var y = Math.min(canvas.height,Math.max(0,Math.round(gaussianRandom(mean_y,deviation))));\n\n      points.push({\"x\": x,\n                 \"y\": y,\n                 \"radius\": POINT_RADIUS,\n                 \"cluster\": classNum ,\n                 \"class\": classNum });\n    }\n\n    mean_x = 3*canvas.width\/4;\n    mean_y = 2*canvas.height\/4;\n    classNum = 2; \n    maxKnearestNeighborClasses++;\n\n    for(var i = 0; i < numPoints; i++) {\n      var x = Math.min(canvas.width,Math.max(0,Math.round(gaussianRandom(mean_x,deviation))));\n      var y = Math.min(canvas.height,Math.max(0,Math.round(gaussianRandom(mean_y,deviation))));\n      \n      points.push({\"x\": x,\n                 \"y\": y,\n                 \"radius\": POINT_RADIUS,\n                 \"cluster\": classNum ,\n                 \"class\": classNum });\n    }\n\n    updateBoard();\n    determineStDev(); \/\/ output standard deviations to console\n}\n\n\/** global variables *\/\nvar maxKnearestNeighborClasses = 1;\nvar INITIAL_RADIUS = 20;\nvar POINT_RADIUS = 5;\nvar points = new Array();\nvar canvas = document.getElementById(\"myCanvas\");\n\nvar alpha_l = parseFloat(document.getElementById(\"alphal\").value);\nvar beta_l = parseFloat(document.getElementById(\"betal\").value);\nvar alpha_u = parseFloat(document.getElementById(\"alphau\").value);\nvar beta_u = parseFloat(document.getElementById(\"betau\").value);\n\nvar kElement = 2;\/\/document.getElementById(\"k\");\nvar context = canvas.getContext(\"2d\");\ndrawBoard(canvas, {\"x\":0,\"y\":0}, INITIAL_RADIUS);\nupdateNumberBgColor();\nsetCursorByID(\"myCanvas\", \"crosshair\");\n\n\/** event listeners *\/\ncanvas.addEventListener('mousemove',\n    function(evt) {\n        var mouseCoords = getMouseCoords(canvas, evt);\n        drawBoard(canvas, mouseCoords, INITIAL_RADIUS);\n    }, false);\n\ncanvas.addEventListener(\"mousedown\",\n    function(event) {\n        var mouseCoords = getMouseCoords(canvas, event);\n        addPoint(event, canvas, mouseCoords, POINT_RADIUS);\n    }, false);\n\n\n\/\/ experimental\nfunction deleteNoisyInstances(threshold) {\n    for (var i = 0; i < points.length; i++) {\n        var classIndex = points[i][\"class\"];\n        var approx = approximations(points[i][\"x\"],points[i][\"y\"],classIndex);\n\n        var measure = approx.lower;\n        \n        if (measure<=threshold) {points[i][\"cluster\"]=-1; console.log(\"noisy\");}\n    }\n}\n\n\n\/\/ Run the LAIR algorithm\n\/\/ num determines which version of LAIR is run\nfunction LAIR(num) {\n    \/\/resetPoints(0);  \/\/ if LAIR was run previously, some points will have been marked black (to be deleted)\n    var perDecision = [];\n\n    \/\/if (num==2)  deleteNoisyInstances(0.005);\n    \n    for (var i=0;i<maxKnearestNeighborClasses;i++) {\n        perDecision[i] = new PriorityQueue2();\n    }\n\n    var considered = [];\n    var instancesToRemove = [];\n\n    for (var i = 0; i < points.length; i++) {\n        var classIndex = points[i][\"class\"];\n        var approx = approximations(points[i][\"x\"],points[i][\"y\"],classIndex);\n\n        var measure = approx.lower;\n        if (num==2) measure = (approx.lower+approx.upper)\/2;\n\n        \/\/console.log(approx.lower+\"  \"+approx.upper);\n\n        perDecision[classIndex-1].add(i,measure);\n    }\n\n    for (var d=0;d<maxKnearestNeighborClasses;d++) {\n        for (var i=0; i<perDecision[d].size();i++) {\n              var next = perDecision[d].remove();\n              var x = next.element; \n\n               \n              if (!(num==2 && x.priority==0) && (considered[x]!=1 && instancesToRemove[x]!=1)) {\n                    considered[x]=1;\n                    \n                    for (var j=0; j<perDecision[d].size();j++) {\n                         var yNext = perDecision[d].elementAt(j);\n                         var y = yNext.element; \n\n                         if (considered[y]!=1) { \n                              var tau = 0.85;\n\n                              tau = 1 - yNext.priority; \/\/ comment this to use the set threshold instead\n\n                              if (similarity(points[x][\"x\"],points[x][\"y\"],points[y][\"x\"],points[y][\"y\"])>tau) instancesToRemove[y]=1;\n                         }\n                    }\n              }\n        }\n    }\n\n    var count=0;\n    for (var i=0;i<instancesToRemove.length;i++) {\n       if (instancesToRemove[i]==1) {points[i][\"cluster\"]=-1;count++;}\n    }\n\n    updateBoard();\n}\n\nfunction get1NNIndex(point) {\n    var bestIndex = 0;\n    var bestDist = 10000;\n\n    for (var i=0;i<points.length;i++) {\n        if (points[i][\"x\"]!=point[\"x\"]&&points[i][\"y\"]!=point[\"y\"]) {\n            var dist = euclideanDist(points[i], point);\n            if (dist<bestDist) {\n                bestDist=dist;\n                bestIndex=i; \n            }\n        }\n    }\n\n    return bestIndex;\n}\n\n\/\/ old attempt at detecting noisy instances\nfunction LAIR4() {\n    for (var i=0;i<points.length;i++) {\n        var index = get1NNIndex(points[i]);\n\n        if (points[index][\"class\"]!=points[i][\"class\"]) {\n            \/\/ see how the temporary removal of the instance i affects the lower approximation of index \n            \/\/ or switch i's class to be the same as that of index, recalculate lower approx\n             \n            var prevClass = points[i][\"class\"];\n            var prevApprox = approximations(points[index][\"x\"], points[index][\"y\"],points[index][\"class\"]);\n\n            points[i][\"class\"] = points[index][\"class\"];\n            var approxNew = approximations(points[index][\"x\"], points[index][\"y\"],points[index][\"class\"]);\n            points[i][\"class\"] = prevClass;\n\n            if (prevApprox.lower < approxNew.lower) {\n                var diff = approxNew.lower - prevApprox.lower;\n                var sim = similarity(points[i][\"x\"], points[i][\"y\"],points[index][\"x\"], points[index][\"y\"]);\n\n                if (diff>=0.1) points[i][\"cluster\"]=-1;\n                console.log(\"----\");\n                \/\/console.log(\"old: \"+implicator(sim,0));\n                console.log(\"old: \"+prevApprox.lower);\n                console.log(\"new: \"+approxNew.lower);\n\n                console.log(\"sim: \"+ sim);\n            }\n\n            \/*var prevClass = points[index][\"class\"];\n            var prevApprox = approximations(points[index][\"x\"], points[index][\"y\"],prevClass);\n\n            points[index][\"class\"] = points[i][\"class\"];\n            var approxNew = approximations(points[index][\"x\"], points[index][\"y\"],points[index][\"class\"]);\n            points[index][\"class\"] = prevClass;\n\n            if (prevApprox.lower < approxNew.lower && prevApprox.upper <= approxNew.upper) points[index][\"cluster\"]=-1;*\/\n        }\n    }\n    updateBoard();\n}\n\n\/\/ Remove noise\n\/\/ For each instance, change its class and determine the lower approximation memberships\n\/\/ If the membership is higher for a different class, then consider removing this instance \nfunction LAIR3() {\n    var pq = new PriorityQueue2();\n\n    for (var i=0; i<points.length; i++) {\n        var result = evaluateClassChange(i);\n\n        if (result.bestClass!=result.actualClass) {\n            \/\/points[i][\"cluster\"]=-1;\n            var difference = result.bestLower-result.actualLower;\n            console.log(i+\": \"+difference);\n            pq.add(i,difference);\n        }\n    }\n\n    console.log(pq.size()+\" instances being considered\");\n    var toKeep = [];\n\n    \/\/ at this point, the problematic instances are stored in pq\n    for (var i=0; i<pq.size(); i++) {\n        var next = pq.elementAt(i);\n        var nextIndex = next.element;\n\n        if (toKeep[nextIndex]!=1) {\n            \/\/ remove instance at next.element\n            console.log(points.length+\" \"+nextIndex);\n\n            points[nextIndex][\"cluster\"]=-1;\n            \/\/points[nextIndex][\"class\"]=-1;\n            \n            \/\/deleted.push(points[nextIndex]);\n            \/\/points.splice(nextIndex,1); \/\/ remove this point\n\n            \/\/ recalculate lower approxs for the remaining elements\n            for (var j=i+1; j<pq.size(); j++) {\n                var nextElem = pq.elementAt(j);\n                var index = nextElem.element;\n\n                var result = evaluateClassChange(index);\n\n                if (result.bestClass==result.actualClass) toKeep[index]=1;\n            }\n        }\n    }\n\n    updateBoard();\n}\n\nfunction evaluateClassChange(i) {\n    var bestLower=0; var actualLower=0;\n    var bestClass=-1;\n    var actualClass = points[i][\"class\"];\n\n    \/\/ see what impact changing the class has on each instance\n    for (var d=1; d<=maxKnearestNeighborClasses; d++) {\n        points[i][\"class\"] = d;\n        var approx = approximations(points[i][\"x\"], points[i][\"y\"],points[i][\"class\"]);\n        points[i][\"class\"] = actualClass;\n        var measure = approx.lower; \/\/ could also include the upper approximation\n\n        \/\/console.log(i+\", \"+d+\": \"+measure + \"  Actual class: \"+actualClass);\n\n        if (d==actualClass) actualLower = measure; \/\/ record the original lower approx membership\n\n        if (measure>bestLower) {\n            bestLower = measure;\n            bestClass = d;\n        }\n    }\n\n    return {\"bestLower\":bestLower, \"bestClass\":bestClass, \"actualClass\":actualClass, \"actualLower\":actualLower};\n} \n\n\/\/ 'type' determines whether to use the lowest lower approx membership or the average lower approx membership\nfunction oversample(percentToGenerate, type) {\n\n    \/\/ for each class, record the lowest lower approx membership to use as a threshold later\n    \/\/ might be better using the average membership as the threshold\n    var lowestLowerApprox = []; \n\n    var x_upper = []; var x_lower = [];\n    var y_upper = []; var y_lower = [];\n\n    var instanceCount = []; \/\/ number of instances per decision class\n    for (var d=1; d <= maxKnearestNeighborClasses; d++) {\n        instanceCount[d]=0;\n        x_upper[d]=0;\n        x_lower[d]=10000000;\n        y_upper[d]=0;\n        y_lower[d]=10000000;\n        if (type==\"average\") lowestLowerApprox[d]=0;\n        else lowestLowerApprox[d]=1;\n    }\n\n    for (var i=0;i<points.length;i++) {\n        var decisionClass = points[i][\"class\"];\n        instanceCount[decisionClass]++;\n        var approx = approximations(points[i][\"x\"],points[i][\"y\"],decisionClass);\n\n        if (type==\"average\") lowestLowerApprox[decisionClass]+=approx.lower;\n        else if (approx.lower<lowestLowerApprox[decisionClass]) lowestLowerApprox[decisionClass]=approx.lower;\n\n        \/\/ work out the range of values of the two features\n        x_upper[decisionClass] = Math.max(x_upper[decisionClass],points[i][\"x\"]);\n        x_lower[decisionClass] = Math.min(x_lower[decisionClass],points[i][\"x\"]);\n        y_upper[decisionClass] = Math.max(y_upper[decisionClass],points[i][\"y\"]);\n        y_lower[decisionClass] = Math.min(y_lower[decisionClass],points[i][\"y\"]);\n    }\n\n    var majorityClass = -1;\n    var majoritySize = -1;\n\n    \/\/ determine the majority class and how many instances belong to it\n    for (var d=1; d <= maxKnearestNeighborClasses; d++) {\n        if (instanceCount[d]>majoritySize) {\n            majoritySize = instanceCount[d];\n            majorityClass = d;\n        }\n\n        if (type==\"average\") lowestLowerApprox[d]\/=instanceCount[d];\n    }\n\n    for (var d=1; d <= maxKnearestNeighborClasses; d++) {\n         if (d==majorityClass) continue; \/\/ ignore the majority class\n\n         var numToGenerate = Math.floor(((percentToGenerate\/100)*majoritySize)-instanceCount[d]);\n         var threshold = lowestLowerApprox[d];\n\n         for (var i=0;i<numToGenerate;i++) {\n             var newPoint = [];\n             \n             newPoint[\"x\"] = randomNumber(x_lower[d],x_upper[d]);\n             newPoint[\"y\"] = randomNumber(y_lower[d],y_upper[d]);\n             newPoint[\"class\"] = newPoint[\"cluster\"] = d;\n\n             var approx = approximations(newPoint[\"x\"],newPoint[\"y\"],d);\n\n             \/\/ adjust if necessary\n             if (approx.lower<=threshold) {\n                 i--; \/\/ try again\n                 console.log(\"weak instance, try again\");\n             }\n             else {\n                 points.push({\"x\": newPoint[\"x\"],\n                 \"y\": newPoint[\"y\"],\n                 \"radius\": POINT_RADIUS,\n                 \"cluster\": d,\n                 \"class\": d});\n             }\n         }\n    }\n\n    updateBoard();\n}\n\nfunction oversampleNeighbors(percentToGenerate) {\n    var instanceCount = []; \/\/ number of instances per decision class\n    var perDecision = [];\n\n    for (var d=1; d <= maxKnearestNeighborClasses; d++) {\n        instanceCount[d]=0;\n        perDecision[d] = new PriorityQueue2();\n    }\n\n    for (var i=0;i<points.length;i++) {\n        var decisionClass = points[i][\"class\"];\n        instanceCount[decisionClass]++;\n    }\n\n    var majorityClass = -1;\n    var majoritySize = -1;\n\n    \/\/ determine the majority class and how many instances belong to it\n    for (var d=1; d <= maxKnearestNeighborClasses; d++) {\n        if (instanceCount[d]>majoritySize) {\n            majoritySize = instanceCount[d];\n            majorityClass = d;\n        }\n    }\n\n    var sums = []; \/\/ keep track of the sum of lower approximation memberships per class\n    for (var d=1; d <= maxKnearestNeighborClasses; d++) sums[d]=0;\n\n    \/\/ calculate lower approximation memberships for all non-majority class instances\n    for (var i=0;i<points.length;i++) {\n        var d = points[i][\"class\"];\n        if (d==majorityClass) continue; \/\/ ignore the majority class\n\n        var approx = approximations(points[i][\"x\"],points[i][\"y\"],d);\n        sums[d]+=approx.lower;\n        perDecision[d].add(i,approx.lower);\n    }\n\n    \/\/ for each non-majority class, generate the required number of instances\n    for (var d=1; d <= maxKnearestNeighborClasses; d++) {\n         if (d==majorityClass) continue; \/\/ ignore the majority class\n\n         var numToGenerate = Math.floor(((percentToGenerate\/100)*majoritySize)-instanceCount[d]);\n\n         for (var i=0; i<numToGenerate; i++) {\n             var probability = sums[d]*Math.random();\n             var ins1 = getChosenInstance(probability,perDecision[d]);\n             probability = sums[d]*Math.random();\n             var ins2 = getChosenInstance(probability,perDecision[d],ins1.element);\n\n\n             \/\/ choose a random point between the two instances\n             \/\/var x_coord = randomNumber(Math.min(points[ins1.element][\"x\"],points[ins2.element][\"x\"]),Math.max(points[ins1.element][\"x\"],points[ins2.element][\"x\"]));\n             \/\/var y_coord = randomNumber(Math.min(points[ins1.element][\"y\"],points[ins2.element][\"y\"]),Math.max(points[ins1.element][\"y\"],points[ins2.element][\"y\"]));\n\n             var ins1Weight = ins1.priority\/(ins1.priority+ins2.priority);\n             var ins2Weight = ins2.priority\/(ins1.priority+ins2.priority);\n             \n             var x_coord = ins1Weight*points[ins1.element][\"x\"] + ins2Weight*points[ins2.element][\"x\"];\n             var y_coord = ins1Weight*points[ins1.element][\"y\"] + ins2Weight*points[ins2.element][\"y\"];\n\n             \/\/console.log(ins1.element + \" \"+ ins2.element+ \": \"+ x_coord+\", \"+y_coord);\n\n             points.push({\"x\": x_coord,\n                 \"y\": y_coord,\n                 \"radius\": POINT_RADIUS,\n                 \"cluster\": d,\n                 \"class\": d});\n         }\n    }\n\n    updateBoard();\n}\n\n\/\/ 'probabilistically' choose an instance from 'list' based on lower approximation memberships\nfunction getChosenInstance(probability, list, prevInstance) {\n     var sumSoFar=0;\n     var next;\n\n     for (var i=0; i<list.size();i++) {\n          next = list.elementAt(i);\n          sumSoFar += next.priority;\n          if (sumSoFar>=probability) {\n              if (next.element==prevInstance) continue; \/\/ move on to the next element\n              else return {\"element\": next.element, \"priority\": next.priority};\n          }\n     }\n\n     next = list.elementAt(0);\n     return {\"element\": next.element, \"priority\": next.priority};\n}\n\nvar k_smote = 5; \/\/ number of nearest neighbours to use for SMOTE\n\nfunction SMOTE(percentToGenerate) {\n    var instanceCount = []; \/\/ number of instances per decision class\n    var perDecision = [];\n\n    for (var d=1; d <= maxKnearestNeighborClasses; d++) {\n        instanceCount[d]=0;\n        perDecision[d] = [];\n    }\n\n    for (var i=0;i<points.length;i++) {\n        var decisionClass = points[i][\"class\"];\n        instanceCount[decisionClass]++;\n        perDecision[decisionClass].push(points[i]);\n    }\n\n    var majorityClass = -1;\n    var majoritySize = -1;\n\n    \/\/ determine the majority class and how many instances belong to it\n    for (var d=1; d <= maxKnearestNeighborClasses; d++) {\n        if (instanceCount[d]>majoritySize) {\n            majoritySize = instanceCount[d];\n            majorityClass = d;\n        }\n    }\n\n    var toAdd = []; \/\/ new points will be added to this, and then the array will be added to the points array at the end\n\n    \/\/ for each non-majority class, generate the required number of instances\n    for (var d=1; d <= maxKnearestNeighborClasses; d++) {\n         if (d==majorityClass) continue; \/\/ ignore the majority class\n\n         var numToGenerate = Math.floor(((percentToGenerate\/100)*majoritySize)-instanceCount[d]);\n\n         for (var i=0; i<numToGenerate; i++) {\n             \/\/ get a random instance from the set of class instances\n             var index = Math.floor(Math.random()*instanceCount[d]);\n             var randomPoint = perDecision[d][index];\n\n             \/\/ determine the nearest neighbours\n             var Distances = new Array();\n             for(var n = 0; n < perDecision[d].length; n++) {             \n                 var dist = euclideanDist(perDecision[d][n], randomPoint);\n                 Distances.push({\"dist\":dist, \"index\":n});\n            }\n\n            numberSort = function (a,b) {\n                return a[\"dist\"] - b[\"dist\"];\n            };\n\n            Distances.sort(numberSort);\n            if (k_smote>perDecision[d].length) k_smote = perDecision[d].length;\n\n            \/\/ choose a random neighbour\n            var randIndex = Math.floor(Math.random()*k_smote);\n            var neighbourIndex = Distances[randIndex][\"index\"];\n            var neighbour = perDecision[d][neighbourIndex];\n\n            var new_x = randomPoint[\"x\"] + (Math.random()*(neighbour[\"x\"] - randomPoint[\"x\"]));\n            var new_y = randomPoint[\"y\"] + (Math.random()*(neighbour[\"y\"] - randomPoint[\"y\"]));\n\n            toAdd.push({\"x\": new_x ,\n                 \"y\": new_y ,\n                 \"radius\": POINT_RADIUS,\n                 \"cluster\": d,\n                 \"class\": d});\n        }\n    }\n\n    for (var i=0;i<toAdd.length;i++) {\n        points.push(toAdd[i]);\n    }\n\n    updateBoard();\n}\n\nfunction randomNumber(min, max) {\n  return Math.random() * (max - min) + min;\n}\n\n\/\/ https:\/\/www.tutorialspoint.com\/The-Priority-Queue-in-Javascript\n class QueueElement {\n     constructor(elem, priNo) {\n         this.element = elem;\n         this.priority = priNo;\n     }\n }\n\n\/\/ adapted from https:\/\/www.tutorialspoint.com\/The-Priority-Queue-in-Javascript\n         class PriorityQueue2 {\n            constructor() {\n               this.queArr = [];\n            }\n            \n            size() {\n               return this.queArr.length;\n            }\n\n            elementAt(i) {\n               return this.queArr[i];\n            }\n\n            add(elem, priNo) {\n               let queueElem = new QueueElement(elem, priNo);\n               let contain = false;\n               for (let i = 0; i < this.queArr.length; i++) {\n                  if (this.queArr[i].priority < queueElem.priority) {\n                     this.queArr.splice(i, 0, queueElem);\n                     contain = true;\n                     break;\n                  }\n               }\n               if (!contain) {\n                  this.queArr.push(queueElem);\n               }\n            }\n\n            remove() {\n               if (!this.isEmpty()) return this.queArr.shift();\n            }\n\n            front() {\n               if (!this.isEmpty()) return this.queArr[0];\n            }\n\n            rear() {\n               if (!this.isEmpty()) return this.queArr[this.queArr.length - 1];\n            }\n\n            isEmpty() {\n               return this.queArr.length == 0;\n            }\n\n            display() {\n               console.log(\"The elements in the priority queue are: \");\n               let res_Str = \"\";\n               for (let i = 0; i < this.queArr.length; i++)\n                    res_Str += this.queArr[i].priority + \" \";\n               console.log(res_Str);\n            }\n         }\n\n\n\/\/ min (smallest element first)\nclass PriorityQueue {\n    constructor() {\n        this.heap = [];\n    }\n \n    \/\/ Helper Methods\n    getLeftChildIndex(parentIndex) {\n        return 2 * parentIndex + 1;\n    }\n \n    getRightChildIndex(parentIndex) {\n        return 2 * parentIndex + 2;\n    }\n \n    getParentIndex(childIndex) {\n        return Math.floor((childIndex - 1) \/ 2);\n    }\n \n    hasLeftChild(index) {\n        return this.getLeftChildIndex(index) < this.heap.length;\n    }\n \n    hasRightChild(index) {\n        return this.getRightChildIndex(index) < this.heap.length;\n    }\n \n    hasParent(index) {\n        return this.getParentIndex(index) >= 0;\n    }\n \n    leftChild(index) {\n        return this.heap[this.getLeftChildIndex(index)];\n    }\n \n    rightChild(index) {\n        return this.heap[this.getRightChildIndex(index)];\n    }\n \n    parent(index) {\n        return this.heap[this.getParentIndex(index)];\n    }\n \n    swap(indexOne, indexTwo) {\n        const temp = this.heap[indexOne];\n        this.heap[indexOne] = this.heap[indexTwo];\n        this.heap[indexTwo] = temp;\n    }\n \n    peek() {\n        if (this.heap.length === 0) {\n            return null;\n        }\n        return this.heap[0];\n    }\n     \n    \/\/ Removing an element will remove the\n    \/\/ top element with highest priority then\n    \/\/ heapifyDown will be called\n    remove() {\n        if (this.heap.length === 0) {\n            return null;\n        }\n        const item = this.heap[0];\n        this.heap[0] = this.heap[this.heap.length - 1];\n        this.heap.pop();\n        this.heapifyDown();\n        return item;\n    }\n \n    add(item) {\n        this.heap.push(item);\n        this.heapifyUp();\n    }\n \n    heapifyUp() {\n        let index = this.heap.length - 1;\n        while (this.hasParent(index) && this.parent(index) > this.heap[index]) {\n            this.swap(this.getParentIndex(index), index);\n            index = this.getParentIndex(index);\n        }\n    }\n \n    heapifyDown() {\n        let index = 0;\n        while (this.hasLeftChild(index)) {\n            let smallerChildIndex = this.getLeftChildIndex(index);\n            if (this.hasRightChild(index) && this.rightChild(index) < this.leftChild(index)) {\n                smallerChildIndex = this.getRightChildIndex(index);\n            }\n            if (this.heap[index] < this.heap[smallerChildIndex]) {\n                break;\n            } else {\n                this.swap(index, smallerChildIndex);\n            }\n            index = smallerChildIndex;\n        }\n    }\n}\n\n\/\/ download a screenshot of the canvas\nfunction screen() {\n    var newCanvas = canvas.cloneNode(true);\n    var ctx = newCanvas.getContext('2d');\n    ctx.fillStyle = \"#FFF\";\n    ctx.fillRect(0, 0, newCanvas.width, newCanvas.height);\n    ctx.drawImage(canvas, 0, 0);\n\n\n    var dataURL = newCanvas.toDataURL(\"image\/jpeg\", 1.0);\n    var a = document.createElement('a');\n    a.href = dataURL ;\n    var d = new Date();\n    a.download = 'screenshot'+d.toISOString()+'.jpeg';\n    a.click();\n}\n\n    \/\/--><\/script>\n","protected":false},"excerpt":{"rendered":"<p>Visualize the fuzzy lower and upper approximations below. (Uses code from here) type standardVQRSOWA relation similarity1similarity1softmsimilarity2similarity2softmsimilarity3similarity3softm KDLukasiewiczAlgebraic class parameter map nonelowerupper spread LAIRLAIR2NoiseRemoveOversample1Oversample2OversampleNSMOTE alphaL betaL alphaU betaU<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":66,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-1081","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/users.aber.ac.uk\/rkj\/site\/wp-json\/wp\/v2\/pages\/1081","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/users.aber.ac.uk\/rkj\/site\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/users.aber.ac.uk\/rkj\/site\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/users.aber.ac.uk\/rkj\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/users.aber.ac.uk\/rkj\/site\/wp-json\/wp\/v2\/comments?post=1081"}],"version-history":[{"count":583,"href":"https:\/\/users.aber.ac.uk\/rkj\/site\/wp-json\/wp\/v2\/pages\/1081\/revisions"}],"predecessor-version":[{"id":2106,"href":"https:\/\/users.aber.ac.uk\/rkj\/site\/wp-json\/wp\/v2\/pages\/1081\/revisions\/2106"}],"up":[{"embeddable":true,"href":"https:\/\/users.aber.ac.uk\/rkj\/site\/wp-json\/wp\/v2\/pages\/66"}],"wp:attachment":[{"href":"https:\/\/users.aber.ac.uk\/rkj\/site\/wp-json\/wp\/v2\/media?parent=1081"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}