post-kerbin-mining-corporation / NearFutureSolar

Adds more solar panel parts to Kerbal Space Program
32 stars 25 forks source link

Round panels don't seem to be accurately modelled #8

Closed pyalot closed 7 years ago

pyalot commented 8 years ago

I was testing the sun exposure/incident angle of round panels and came upon something strange (it's possible to following observation is limited by KSP and it's not your fault, nevertheless it's good to record).

The test setup I used to take these measurements:

2015-12-26_00038

The measurements for a round panel (NV-3) and a flat panel (PX-Stat 1x2), the other columns are estimates for flat panel (Cos(A)) and estimates for round panels. Here is the raw data:

Angle NIV-3 (round) PX-Stat 1x2 (flat) Cos(a) Theoretical 90° round Theoretical 80° round Theoretical 40° Round
0 0.95 1 1 0.8983655217 0.9191635096 0.9794102871
10 0.94 0.98 0.984807753 0.8847173308 0.9051993505 0.9645308441
20 0.89 0.94 0.9396926208 0.8441874515 0.8637311673 0.9203446195
30 0.82 0.86 0.8660254038 0.7780073637 0.7960189496 0.8481941894
40 0.72 0.76 0.7660444431 0.6881879158 0.7041200989 0.750271808
50 0.6 0.63 0.6427876097 0.5803120933 0.5908269153 0.6295527974
60 0.46 0.49 0.5 0.4719716087 0.4712381839 0.4897051436
70 0.33 0.33 0.3420201433 0.3684406425 0.3588551084 0.3349780468
80 0.23 0.16 0.1736481777 0.2728649306 0.257109921 0.1924912522
90 0.12 0 0 0.1881484927 0.1691115598 0.08723867911
100 0.06 0 0 0.1168653949 0.09749885254 0.02241844065
110 0.01 0 0 0.06118153801 0.04448267314 6.12E-19
120 0 0 0 0.02278884786 0.0116564291 0
130 0 0 0 0.00285386692 6.12E-19 0
140 0 0 0 0 0 0
150 0 0 0 0 0 0

The theoretical estimates for round panels where created using this python program that divides round panels up into 100 facets and computes their sum exposure:

import math

def flatPanelExposure(exposureAngle):
    rad = math.pi*2.0*exposureAngle/360.0
    return max(0.0, math.cos(rad))

def roundPanelExposure(exposureAngle, circularCoverage):
    count = 0.0
    amount = 0.0
    for facetNum in range(0,100):
        facetNum = float(facetNum)
        facetScalar = facetNum/99.0
        facetAngle = circularCoverage*facetScalar-circularCoverage*0.5
        angle = exposureAngle + facetAngle
        amount += flatPanelExposure(angle)
        count += 1.0
    return amount/count

for exposureAngle in range(0,160,10):
    exposureAngle = float(exposureAngle)
    #print exposureAngle, flatPanelExposure(exposureAngle+45)
    #print exposureAngle, roundPanelExposure(exposureAngle, 90.0)
    print roundPanelExposure(exposureAngle, 40.0)

Plotting for a flat panel we see that the flat panel matches estimated performance quite closely:

flat-panel

Judging the NV-3 panel from the looks/curvature it seems to be designed to cover a 90° arc. However I observed that visually (when rotated) it seems to be more like 80°, and from observation it already cuts out energy flow for a coverage that'd correspond to 40°.

Comparing the round panel to cos(a) and for the 90° estimate, we can see that it matches neither (it should really match the 90° estimate).

round-panel-comparison

Comparing the round panel to 90°, 80° and 40° we can see that its actual performance is more closely aligned with the 40° estimate (maybe a bit above that, like 45°).

round-panel-comparison2

It might be a bit expensive to estimate a round panel using a hundred facets, so I tested which facet count still provides a fairly good estimate (the program is slightly modified for that to account correctly for the half facets at the start and end and have a parameter for facet count, here's the updated estimation function):

def roundPanelExposure(exposureAngle, circularCoverage, facetCount):
    count = 0.0
    amount = 0.0
    for facetNum in range(0,facetCount+1):
        if facetNum == 0 or facetNum == facetCount:
            weight = 0.5
        else:
            weight = 1.0
        facetNum = float(facetNum)
        facetScalar = facetNum/facetCount
        facetAngle = circularCoverage*facetScalar-circularCoverage*0.5
        angle = exposureAngle + facetAngle
        amount += flatPanelExposure(angle)*weight
        count += weight
    return amount/count

And here is the result:

facet-comparison

Down to 8 facets things look fairly good, but at 4 facets it gets kinda bad.

It'd also be possible to approximate with a trigonometric function, use calculus (I'm very bad at calculus), or use curve fitting (cool online tool for that http://mycurvefit.com/) of a polynominal (quadric or quintic or some spline/smoothstep). Although it's doubtful the maths to evaluate those curve fits are any much cheaper than taking 8 facets for sampling.

ChrisAdderley commented 8 years ago

You're right, it's computationally expensive. Not so much to calculate sun exposure, dot products are cheap, but to calculate occlusion from parts and nearby terrain is more expensive, particularly in a Unity-based game with very expensive raycasting. So the panels only use 3-6 points depending on radius size. It seems like a pretty good compromise, particularly given plot 2, which shows about a 10% deviation at most. I'm happy enough with this. With 4 raycast points, a full circle of panels is already 16 raycasts per tick (a tick is longer in this case), which is already at least 4x as expensive as a stock solar panel.

Yes, I could model it analytically using calc, but this is actually more versatile. Since the occlusion points are specified on the model, it's easy to use it for panels that are different in geometry to arcs. The module can theoretically do any shape of panel this way.

pyalot commented 8 years ago

I see your point about casting and occlusion cost, still there's something odd going on. Even if you just use 6 points, the curve should still match more closely (after all the 4-facet model still resembles the ideal curve much more closely).

I also see the point about flexibility and an analytic solution. But it'd still be possible to plug an analytic approximation for panels segments and make up your panels of multiples of those (like say 18° segments).

ChrisAdderley commented 8 years ago

Where are you placing the facet points in your plots? That could affect things, as I've placed the source transforms manually and probably aren't perfectly distributed.

pyalot commented 8 years ago

The revised code places half a facet at the extremes and a full facet inbetween each evenly distributed, see the second code snippet.

ChrisAdderley commented 8 years ago

Yeah that's different than my distribution, which places each facet so that each point represents a full face with no halves. That might be the reason there is a difference. (for 90 degrees of arc, one is at 15, 45 and 75)

pyalot commented 8 years ago

Even so, there's still something odd. Revised the code a bit again and test both facet placement methods:

def roundPanelExposure(exposureAngle, arcCoverage, facetCount):
    count = 0.0
    amount = 0.0
    for facetNum in range(0,facetCount):
        if facetNum == 0 or facetNum == facetCount-1:
            weight = 0.5
        else:
            weight = 1.0

        facetNum = float(facetNum)
        facetScalar = facetNum/(facetCount-1)
        facetAngle = arcCoverage*facetScalar-arcCoverage*0.5
        angle = exposureAngle + facetAngle
        amount += flatPanelExposure(angle)*weight
        count += weight
    return amount/count

def roundPanelExposureAllFull(exposureAngle, arcCoverage, facetCount):
    count = 0.0
    amount = 0.0
    endsOffset = (arcCoverage/facetCount)*0.5
    arc = arcCoverage-endsOffset*2.0

    for facetNum in range(0,facetCount):
        facetNum = float(facetNum)
        facetScalar = facetNum/(facetCount-1)
        facetAngle = endsOffset + facetScalar*arc - arcCoverage*0.5
        angle = exposureAngle + facetAngle
        amount += flatPanelExposure(angle)
        count += 1.0
    return amount/count

Comparing 4-facets, it's not terribly good, but still much closer than the observed result.

4-facet-methods

6-facets is really close:

6-facet-methods

ChrisAdderley commented 8 years ago

I'm pretty confident in saying this is just due to lazy "facet" transform placement by me. Regardless, it's nowhere near serious enough to invest the time to fix it now. I will leave the issue open though to remind me to take a look when I next open up those source files though.

ChrisAdderley commented 8 years ago

Will be reviewing the positions of the transforms for 0.5.6

pyalot commented 8 years ago

Be sure to use a test-rig as illustrated using Infernal Robotics to review your results. By setting appropriate pre-sets for the rotation and picking the time of day just right you can get quite good readings (fiddling by hand to move to the angles tends to be a lot less accurate).

ChrisAdderley commented 8 years ago

Just so you know, I don't really intend to do a perfect analysis or simulation. I merely aim to look at the positions of the sun transforms and back-of-the-envelope determine if that's what is causing a discrepancy (it will be obvious) and adjust the positions a bit if it's egregiously bad.

pyalot commented 8 years ago

Sure, but proper methodology for addressing an issue is to 1) measure 2) fix. You'll want to exclude as much uncertainty as reasonably possible from measuring so you can see if you've made progress in your fix. Otherwise fuzz/noise is going to overshadow your results and you wasted your time fixing something because you didn't have an appropriate means to measure if you've made progress.

Using Infernal Robotics, setting 10 presets for a rotatron, making a 4-part test rig, adjusting your time of day appropriately and noting down 10 data points in a spreadsheet is by my estimation quite a reasonable effort as it'll take you about 10 minutes to do, compared to many more minutes spent fiddling with the asset...

Also keep in mind that I've provided you with code/data to compare your fix against, so you don't need to invent the wheel in terms of reference data either.