Compare commits

...

2 Commits

Author SHA1 Message Date
Sebastián Santisi 67a6c99e55 Validación 2 years ago
Sebastián Santisi 89c34931ad Simulación de todas las direcciones 2 years ago

@ -64,7 +64,7 @@ function follow(e) {
<div id="follower"></div>
<div id="results"></div>
<div id="popup">
<div id="close" onclick="hide();"></div>
<div id="close" onclick="hide();" onmousedown="window.event.stopPropagation();" onmouseup="window.event.stopPropagation();" ontouchstart="window.event.stopPropagation();" ontouchend="window.event.stopPropagation();"></div>
<div id="text">
<center><img src="assets/logooscuro.png" style="width: 25vh;" /></center>
<h3>Simulador de Aerogeneradores y Parques Eólicos (SAPE)</h3>
@ -94,6 +94,7 @@ function follow(e) {
<li title="Sobre SAPE" class="sel" onclick="help();" style="cursor: help">?</li>
</ul>
<div id="button" onclick="ws.simulate();">¡SIMULAR!</div>
<div id="button_validate" onmousedown="window.event.stopPropagation();" onmouseup="window.event.stopPropagation();" ontouchstart="window.event.stopPropagation();" ontouchend="window.event.stopPropagation();" onclick="window.event.stopPropagation(); ws.validate();">¡VALIDAR!<br/><span style="font-size: .5em;padding: 0;">con todos los vientos</span></div>
</div>
<div id="copyright">&copy;2024 Centro de Simulación Computacional para Aplicaciones Tecnológicas (CSC) - CONICET</div>
<div id="rotate"><img src="assets/rotar_telefono.png" /><p>Rotá tu<br />dispositivo</p></div>

@ -15,6 +15,9 @@ const renderer = new THREE.WebGLRenderer({antialias: true});
document.body.appendChild(renderer.domElement);
//renderer.setPixelRatio( window.devicePixelRatio );
const EXCLAMATION = '⚠️';
const PARTY = '🥳';
class WindSimulation {
constructor(width, height, mills) {
this.width = width;
@ -26,6 +29,7 @@ class WindSimulation {
this.wm = null;
this.simulation = null;
this.simulationPots = null;
this.simulationPot = null;
this.xhr = null;
@ -139,7 +143,7 @@ class WindSimulation {
var i = this.wms.indexOf(wm);
var follower = document.getElementById("follower");
var perc = Math.round(this.simulationPot[i] * 100 / 14.95);
follower.innerHTML = "<h3>Aerogenerador Nº" + (i + 1) + "</h4>" + (Math.round(this.simulationPot[i] * 100) / 100) + ' GWh<br /><div class="bar" style="background: linear-gradient(to right, ' + (perc >= 80 ? '#fff' : '#f00') + ' ' + perc + '%, #000 ' + perc + '%);"></div><br />' + perc + '% ' + (perc < 50 ? '⚠️' : '');
follower.innerHTML = "<h3>Aerogenerador Nº" + (i + 1) + "</h4>" + (Math.round(this.simulationPot[i] * 100) / 100) + ' GWh<br />' + this.divPercentBar(perc, 50) + '<br />' + perc + '% ' + (perc < 50 ? '⚠️' : '');
follower.style.display = "block";
}
}
@ -174,12 +178,14 @@ class WindSimulation {
resetSimulation() {
scene.remove(this.simulation);
this.simulation = null;
this.simulationPots = null;
this.simulationPot = null;
for(var i = 0; i < this.mills; i++)
this.wms[i].lowPower(false);
document.getElementById("follower").style.display = "none";
document.getElementById("results").style.display = "none";
document.getElementById("numbers").innerHTML = "";
document.getElementById("button_validate").style.display = "none";
}
setRotation(dir) {
@ -243,7 +249,58 @@ class WindSimulation {
xhr.send(data);
}
add_simulation(ws, pot) {
validate() {
if(this.simulationPots == null) return;
var html = '<p>En un parque real tenemos que garantizar que la posición de los aerogeneradores sea la adecuada para todos los vientos posibles.</p><p>Este es el resultado de ensayar tu configuración con los 4 vientos disponibles en este simulador:</p><center><table><tr><th>Aerogenerador</th><th>Viento <div class="b">⬅</div></th><th>Viento <div class="b">⬉</div></th><th>Viento <div class="b">⬆</div></th><th>Viento <div class="b">⬈</div></th></tr>';
var pots = {0: 0, 225: 0, 270: 0, 315: 0}
for(let i = 0; i < this.simulationPot.length; i++) {
html += '<tr><td><b>Nº' + (i + 1) + '</b></td>';
[0, 315, 270, 225].forEach(pot => {
var val = this.simulationPots[pot][i];
pots[pot] += val;
var perc = val / 14.95 * 100;
html += '<td>' + this.divPercentBar(perc, 50) + ' ' + Math.round(perc) + '%</td>';
});
html += "</tr>";
}
html += '<tr class="last"><td><b>TOTAL</b></td>';
var notValid = [];
[0, 315, 270, 225].forEach(pot => {
var perc = pots[pot] / 14.95 * 100 / this.simulationPot.length;
if(perc < 80) notValid.push(pot);
html += '<td>' + this.divPercentBar(perc, 80) + ' ' + Math.round(perc) + '%</td>';
});
html += "</table></center>";
if(notValid.length) {
html += '<div style="float: left; font-size: 3em; padding-right: .3em;">' + EXCLAMATION + '</div><p>Para ser una configuración correcta hay que superar el 80% de producción para todas las direcciones de vientos.</p><p>Tu simulación no valida con ' + (notValid.length > 1 ? 'las direcciones ' : 'la dirección ');
var dirs = {0: '⬅', 315: '⬉', 270: '⬆', 225: '⬈'};
for(let i = 0; i < notValid.length; i++) {
html += '<span style="display: inline-block;" class="b">' + dirs[notValid[i]] + '</span>';
}
html += '.</p><p>Usá los botones de arriba a la derecha para simular tu parque con cada una de las direcciones asegurándote de cumplir con el objetivo.</p>';
}
else {
html += '<div style="float: left; font-size: 3em; padding-right: .3em;">' + PARTY + '</div><p>¡Felicitaciones! Tu configuración valida una producción de al menos el 80% para todas las direcciones de vientos del simulador.</p>';
if(this.simulationPot.length < 10)
html += '<p>Utilizá el botón <span class="b">+</span> para agregar más aerogeneradores y producir más energía en tu parque.</p>';
else
html += '<p>¡SAPE! Lograste hacer funcionar tu parque con el máximo de aerogeneradores que permite este simulador.</p>';
}
window.show(html);
}
divPercentBar(perc, limit) {
return '<div class="bar" style="background: linear-gradient(to right, ' + (perc >= limit ? '#fff' : '#f00') + ' ' + perc + '%, #0000 ' + perc + '%);"></div>';
}
add_simulation(ws, pots) {
var pot = pots[this.dir];
var sum = 0;
for(let i = 0; i < pot.length; i++) {
this.wms[i].lowPower(pot[i] < 14.95 * 0.5);
@ -258,10 +315,10 @@ class WindSimulation {
for(let i = 0; i < pot.length; i++) {
var perc = Math.round(pot[i] / 14.95 * 100);
innerHTML += '<tr><td><b>Nº' + (i + 1) + ':</b></td><td>' + Math.round(pot[i] * 100) / 100 + ' GWh</td><td><div class="bar" style="background: linear-gradient(to right, ' + (perc >= 50 ? '#fff' : '#f00') + ' ' + perc + '%, #000 ' + perc + '%);"></div> ' + perc + '% ' + (perc < 50 ? '⚠️' : '') + '</td></tr>'
innerHTML += '<tr><td><b>Nº' + (i + 1) + ':</b></td><td>' + Math.round(pot[i] * 100) / 100 + ' GWh</td><td>' + this.divPercentBar(perc, 50) + ' ' + perc + '% ' + (perc < 50 ? '⚠️' : '') + '</td></tr>'
}
var perc = Math.round(sum / 14.95 / pot.length * 100);
innerHTML += '<tr class="last"><td>TOTAL:</td><td>' + Math.round(sum * 100) / 100 + ' GWh</td><td><div class="bar" style="background: linear-gradient(to right, ' + (perc >= 80 ? '#fff' : '#f00') + ' ' + perc + '%, #000 ' + perc + '%);"></div> ' + perc + '% ' + (perc < 80 ? '⚠️' : '') + '</td></tr></table>';
innerHTML += '<tr class="last"><td>TOTAL:</td><td>' + Math.round(sum * 100) / 100 + ' GWh</td><td>' + this.divPercentBar(perc, 80) + ' ' + perc + '% ' + (perc < 80 ? '⚠️' : '') + '</td></tr></table>';
results.innerHTML = innerHTML;
results.style.display = "block";
@ -292,7 +349,11 @@ class WindSimulation {
if(this.simulation)
this.resetSimulation();
document.getElementById("button_validate").style.display = "block";
this.simulation = mesh;
this.simulationPots = pots;
this.simulationPot = pot;
scene.add(mesh);
}

@ -11,82 +11,60 @@ import xarray as xr
D = 126 #Diametro del wT
h = 90 #Altura del WT
U_ref = 8
ws = 8
initial_position = np.array([[1, 1]])
initial_position = np.array([[ 1 , 1],
[ 2. , 2],
[ 4 , 4],
[ 1 , 4],
[ 2 , 4],
[ 3 , 4],
[ 1 , 5],
[ 2 , 5],
[ 5 , 5]])
def py_wake_Initial_Cong(D,name,h,U_ref,initial_position=initial_position):
#Curvas del WT
def py_wake_Initial_Conf(D, name, h):
power_curve = np.array([
[ 0.1, 1.000],
[ 4.5, 267.7],
[ 5.0, 387.6],
[ 5.5, 534.0],
[ 6.0, 707.4],
[ 6.5, 910.0],
[ 7.0, 1142.7],
[ 7.5, 1407.5],
[ 8.0, 1707.1],
[ 8.5, 2047.3],
[ 9.0, 2430.6]]) * [1, 1000]
[ 0.1, 1.000],
[ 4.5, 267.7],
[ 5.0, 387.6],
[ 5.5, 534.0],
[ 6.0, 707.4],
[ 6.5, 910.0],
[ 7.0, 1142.7],
[ 7.5, 1407.5],
[ 8.0, 1707.1],
[ 8.5, 2047.3],
[ 9.0, 2430.6]]) * [1, 1000]
ct_curve = np.array([
[ 0.1, 0.100],
[ 4.5, 0.928],
[ 5.0, 0.892],
[ 5.5, 0.861],
[ 6.0, 0.835],
[ 6.5, 0.812],
[ 7.0, 0.792],
[ 7.5, 0.776],
[ 8.0, 0.7702530978349217],
[ 8.5, 0.762],
[ 9.0, 0.763]])
return WindTurbine(name=name,
diameter=D,
hub_height=h,
powerCtFunction=PowerCtTabular(power_curve[:, 0],
power_curve[:, 1],
'w',ct_curve[:, 1],
method='linear'))
[ 0.1, 0.100],
[ 4.5, 0.928],
[ 5.0, 0.892],
[ 5.5, 0.861],
[ 6.0, 0.835],
[ 6.5, 0.812],
[ 7.0, 0.792],
[ 7.5, 0.776],
[ 8.0, 0.7702530978349217],
[ 8.5, 0.762],
[ 9.0, 0.763]])
powerct = PowerCtTabular(power_curve[:, 0], power_curve[:, 1], 'w',ct_curve[:, 1], method='linear')
return WindTurbine(name=name, diameter=D, hub_height=h, powerCtFunction=powerct)
windTurbines = py_wake_Initial_Cong(D, 'NREL_5MW', h, U_ref)
windTurbines = py_wake_Initial_Conf(D, 'NREL_5MW', h)
grid = XYGrid(x=np.arange(0, 10.01, 0.1)*D, y=np.arange(0, 10.01, 0.1)*D)
def run(direction=0, initial_position=initial_position, U_ref=U_ref):
def run(direction=0, initial_position=initial_position, ws=ws):
initial_position = np.array(initial_position);
p_wd = [0] * 360
#p_wd[direction * n // 360] = 1
p_wd[direction] = 1
site = UniformSite(p_wd=p_wd,
ws=U_ref,
initial_position=initial_position*D)
p_wd[0] = p_wd[225] = p_wd[270] = p_wd[315] = 1
site = UniformSite(p_wd=p_wd, ws=ws, initial_position=initial_position*D)
#ds = xr.Dataset(
# data_vars={'P': ('wd', [1, 1, 1, 1])},
# coords={'wd': [0, 90, 180, 270]})
#ds['TI'] = 0.1
#site = XRSite(ds, interp_method='nearest', initial_position=initial_position*D, default_ws=np.atleast_1d(U_ref))
#site = XRSite(ds, interp_method='nearest', initial_position=initial_position*D, default_ws=np.atleast_1d(ws))
wt_x, wt_y = site.initial_position.T/D
wt_x, wt_y = site.initial_position.T
wfm = PropagateDownwind(site, windTurbines, wake_deficitModel=TurboGaussianDeficit())
xa = wfm(x=wt_x*D, y=wt_y*D, wd=direction, yaw=0).flow_map(grid)
xa = wfm(x=wt_x, y=wt_y, wd=direction, yaw=0).flow_map(grid)
ws = xa.WS_eff
aep = wfm(x=wt_x*D, y=wt_y*D).aep()
aep = wfm(x=wt_x, y=wt_y).aep()
return ws[:,:,0,0].values[:,:,0].tolist(), [sum(aep.values[i,:,0]) for i in aep.wt.values]
#return ws[:,:,0,0].values[:,:,0].tolist(), list(aep.values[:,direction,0])
return ws[:,:,0,0].values[:,:,0].tolist(), {dir: list(aep.values[:,dir,0]) for dir in (0, 225, 270, 315)}

@ -77,6 +77,19 @@ h3, h4 {
margin: 0 .2vh;
}
#popup table {
text-align: center;
white-space: nowrap;
}
#popup td {
padding-left: 1em;
}
#popup td:first-child {
padding-left: 0;
}
#close {
text-align: right;
font-weight: 900;
@ -104,6 +117,19 @@ h3, h4 {
background: blue;
}
#button_validate {
display: none;
margin-top: 1vh;
padding: 2vh 4vh;
background: darkred;
font-weight: 800;
line-height: .5em;
}
#button_validate:hover {
background: red;
}
#right {
position: absolute;
max-width: 75vw;

Loading…
Cancel
Save