Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
blob: d12d6c3e121c9902e581493119070d454cd9d577 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
  Copyright (C) 2015 R-T Specialty, LLC.

  This file is part of tame-core.

  tame-core is free software: you can redistribute it and/or modify it
  under the terms of the GNU General Public License as
  published by the Free Software Foundation, either version 3 of the
  License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-->
<package xmlns="http://www.lovullo.com/rater"
  xmlns:c="http://www.lovullo.com/calc"
  xmlns:t="http://www.lovullo.com/rater/apply-template"
  core="true"
  desc="Interpolation">

  <import package="../base" />
  <import package="../numeric/common" />


  <template name="_interpolate_" desc="Interpolate the first two values in a vector">
    <param name="@values@" desc="Set" />
    <param name="@low@" desc="Lower value" />
    <param name="@high@" desc="Upper value" />
    <param name="@actual@" desc="Actual value" />


    <c:let>
      <c:values>
        <!-- allows us to reference the set values -->
        <c:value name="orig_set" type="float" set="vector" desc="The given set">
          <param-copy name="@values@" />
        </c:value>
      </c:values>

      <c:let>
        <c:values>
          <!-- ensure that the set is ordered such that the lower value is the first index -->
          <c:value name="set" type="float" set="vector" desc="Ordered set">
            <c:let>
              <c:values>
                <c:value name="a" type="float" desc="First set value">
                  <c:car>
                    <c:value-of name="orig_set" />
                  </c:car>
                </c:value>

                <c:value name="b" type="float" desc="Second set value">
                  <c:value-of name="orig_set">
                    <c:index>
                      <c:const value="1" type="integer" desc="Second index" />
                    </c:index>
                  </c:value-of>
                </c:value>
              </c:values>

              <c:cases>
                <!-- when a > b, reorder -->
                <c:case>
                  <c:when name="a">
                    <c:gt>
                      <c:value-of name="b" />
                    </c:gt>
                  </c:when>

                  <c:set label="Re-ordered set such that the lower value is first in the vector">
                    <c:value-of name="b" />
                    <c:value-of name="a" />
                  </c:set>
                </c:case>

                <!-- already ordered -->
                <c:otherwise>
                  <c:value-of name="orig_set" />
                </c:otherwise>
              </c:cases>
            </c:let>
          </c:value>

          <!-- determine the skip to use for the vecstep call -->
          <c:value name="skip" type="float" desc="First value to be used as skip">
            <c:value-of name="@low@" />
          </c:value>

          <!-- determine the step to use for the vecstep call -->
          <c:value name="step" type="float" desc="Use difference between the first two as the step">
            <c:sum label="Step between the low and high values">
              <c:value-of name="@high@" />

              <t:negate>
                <c:value-of name="@low@" />
              </t:negate>
            </c:sum>
          </c:value>
        </c:values>

        <c:cases>
          <!-- check to see if interpolation is even necessary; in particular,
               this will prevent a step of 0 to vecstep, which would eventually
               result in a division by 0 -->
          <c:case>
            <c:when name="step">
              <c:eq>
                <c:const value="0" type="integer" desc="No step indicates identical values" />
              </c:eq>
            </c:when>

            <!-- just return the first value; it's exact and no interpolation is necessary -->
            <c:value-of name="set">
              <c:index>
                <c:const value="0" type="integer" desc="First index" />
              </c:index>
            </c:value-of>
          </c:case>

          <!-- values are inexact; interpolation is required -->
          <c:otherwise>
            <!-- give the values computed above, we can re-use vecstep on the first two values on the vector -->
            <c:apply name="vecstep" set="set" skip="skip" step="step" value="@actual@" />
          </c:otherwise>
        </c:cases>
      </c:let>
    </c:let>
  </template>


  <!--
    Perform interpolation on the results of a table query

    Not only is interpolation itself obnoxious, but determining the values
    to look up from a table in order to get the data *to* interpolate also
    results in a great deal of boilerplate code. This makes you not want to
    kill yourself. At least not because of this interpolation query
  -->
  <template name="_interpolate-query-field_"
               desc="Interpolate table data">
    <param name="@table@"  desc="Table to query" />
    <param name="@field@"  desc="Table field to query" />
    <param name="@key@"    desc="Predicate subject column" />
    <param name="@step@"   desc="Key step" />
    <param name="@values@" desc="Query predicates" />

    <param name="@actual@"
              desc="Actual value" />

    <param name="@index@" desc="Actual value index">
      <text></text>
    </param>

    <param name="@table_max@"
              desc="Maximum value in table (should be a multiple of @step)" />

    <!-- TODO: accept a function to calculate factor -->
    <param name="@step_factor@"
              desc="Factor to use per step after maximum to infer value" />
    <param name="@step_factor_index@"
              desc="Index of step factor value">
      <text></text>
    </param>


    <c:let>
      <c:values>
        <!-- if the requested limit exceeds the maximum value we support in
             the table, then look up the maximum -->
        <c:value name="lookup" type="float"
                 desc="Value to retrieve and interpolate">
          <!-- maximum provided -->
          <if name="@table_max@">
            <t:cap name="@table_max@">
              <c:value-of name="@actual@" />
            </t:cap>
          </if>

          <!-- no maximum -->
          <unless name="@table_max@">
            <c:value-of name="@actual@" index="@index@" />
          </unless>
        </c:value>
      </c:values>


      <c:sum>
        <!-- lookup -->
        <t:let-round name="lookup"
                     step="@step@"
                     high="high"
                     low="low">
          <!-- query and interpolate -->
          <t:interpolate low="low"
                         high="high"
                         actual="lookup">
            <t:query-field table="@table@"
                           field="@field@">
              <!-- query for upper and lower values for interpolation -->
              <t:when field="@key@">
                <c:value-of name="low" />
                <c:value-of name="high" />
              </t:when>

              <param-copy name="@values@" />
            </t:query-field>
          </t:interpolate>
        </t:let-round>

        <!-- step factor -->
        <if name="@step_factor@">
          <c:let>
            <c:values>
              <!-- additional value (of key) that needs to be infered -->
              <c:value name="add" type="float"
                       desc="Additional value">
                <c:sum>
                  <c:value-of name="@actual@" index="@index@" />
                  <t:negate>
                    <c:value-of name="lookup" />
                  </t:negate>
                </c:sum>
              </c:value>
            </c:values>


            <c:product>
              <c:value-of name="@step_factor@"
                          index="@step_factor_index@" />
              <c:quotient>
                <c:value-of name="add" />
                <c:value-of name="@step@" />
              </c:quotient>
            </c:product>
          </c:let>
        </if>
      </c:sum>
    </c:let>
  </template>


  <!--
    Calculates a floating-point representation of an arbitrary position within a
    vector of values, where the value may exist between two elements within the
    vector.

    For example, given the following vector with the value of 12.5:

      [ 5   ]
      [ 10  ]
             <- value is somewhere in here
      [ 15  ]
      [ ... ]
      [ 100 ]

    (Since the vector is 0-indexed, and we know that the index of 10 and 15 are
    1 and 2 respectively, a value halfway between them would have an index of
    exactly 1.5; so that's the value we're looking for.)

    To calculate its position, given a 0-indexed vector, we need to know (a) the
    step between each value and (b) the offset of the first step (the skip). In
    the above case, index 0 represents 5, so let's assume that the skip provided
    to us is 5. In this case, we have:

      12.5 - 5 = 7.5

    Which is the value without the skip. We can now simply divide it by the
    step:

      7.5 / 5 = 1.5, Q.E.D.
  -->
  <function name="vecpos" desc="Calculate the position of a value within a vector of a given step">
    <param name="step"  type="float" desc="Step between each of the values" />
    <param name="skip"  type="float" desc="Amount skipped before first element of vector" />
    <param name="value" type="float" desc="Arbitrary value" />

    <c:quotient>
      <c:sum>
        <c:value-of name="value" />
        <t:negate>
          <c:value-of name="skip" />
        </t:negate>
      </c:sum>

      <c:value-of name="step" />
    </c:quotient>
  </function>


  <!--
    Calculates any arbitrary value given a vector of values and the step between
    those values

    This function is best demonstrated with an example. Consider the following
    vector: [ 0, 5, 10, ..., 100 ]T. From this, we can see that the step is 5
    and the skip is 0. (If the vector would have started at 5, then the skip
    would have been 5.) The value is any arbitrary value.

    This performs an index calculation and then calls vecstepi for further
    processing; see vecstepi and vecpos for further information. Once the
    relative position within the vector is calculated, we no longer need the
    step and skip amounts, since they were only needed to determine the value's
    position.
  -->
  <function name="vecstep" desc="Calculate a value that falls between a vector of values at a given step">
    <param name="set"   type="float" set="vector" desc="Vector of values" />
    <param name="step"  type="float"              desc="Step between each of the values" />
    <param name="skip"  type="float"              desc="Amount skipped before first element of vector" />
    <param name="value" type="float"              desc="Arbitrary value" />

    <!-- call the function that will do the actual work -->
    <c:apply name="vecstepi" set="set">
      <!-- this is why the function call is necessary; we do not support
           variable assignments, so the only way to have the same effect is to
           invoke another function with the assignment as an argument (this
           value is used frequently) -->
      <c:arg name="pos">
        <c:apply name="vecpos" step="step" skip="skip" value="value" />
      </c:arg>
    </c:apply>
  </function>


  <!--
    Calculates any arbitrary value given a vector of values and an arbitrary
    index

    (Continuing from vecstep): if we were given the same vector [ 0, 5, 10, ...,
    100 ]T and the calculated index 2 (assuming that the vectors are 0-indexed),
    that would yield the same upper and lower value (5 and 5). The result would
    then be ( 5 + ( 5-5 * 2-2 ) ) = 5. That's not very impressive, so let's
    consider a more complicated case.

    Consider that the given position is 2.5. By taking the floor and ceiling of
    this position, we arrive at indexes 2 and 3, which yields the values 10 and
    15 respectively. Our goal is to determine what the value (v) at position 2.5
    should be:
      - Start with the lower value; we'll be adding atop of this.
      - Consider the remaining values (upper - lower): 15 - 10 = 5.
        This means that we'll be adding some value between 0 and 5 such that the
        value is 50% of the way (remember, we had 2.5) between the two. So,
        we're looking for the value 2.5.
      - To get this value, we can multiply the difference in the upper and lower
        bounds by the decimal portion of our position (because 5 * 0.50 = 2.5).
        To get the decimal portion, we can simply subtract the floor of the
        position from the position itself: 2.5 - 2 = 0.5.
      - So, we have: 5 + ( 15-10 * 2.5-2 ) = 5 + ( 5 * 0.5 ) = 5 + 2.5 = 7.5.
  -->
  <function name="vecstepi" desc="Calculate a value that falls between a vector of values at an arbitrary index">
    <param name="set"  type="float" set="vector" desc="Vector of values" />
    <param name="pos"  type="float"              desc="Position of value within the vector (may be between values)" />

    <c:sum>
      <!-- add the closest lower value... -->
      <c:value-of name="set">
        <c:index>
          <c:floor label="Lower index">
            <c:value-of name="pos" />
          </c:floor>
        </c:index>
      </c:value-of>

      <c:product label="Partial value to apply atop of the lower base value">
        <c:sum label="Subtract the upper and lower values to get the increment per step">
          <c:value-of name="set" label="Upper value">
            <c:index>
              <c:ceil>
                <c:value-of name="pos" />
              </c:ceil>
            </c:index>
          </c:value-of>

          <t:negate>
            <c:value-of name="set" label="Lower value">
              <c:index>
                <c:floor>
                  <c:value-of name="pos" />
                </c:floor>
              </c:index>
            </c:value-of>
          </t:negate>
        </c:sum>

        <!-- determine the % between the two values -->
        <c:sum label="Dropping the whole number from the position (yielding only the decimal) gives us the % difference">
          <c:value-of name="pos" />
          <t:negate>
            <c:floor>
              <c:value-of name="pos" />
            </c:floor>
          </t:negate>
        </c:sum>
      </c:product>
    </c:sum>
  </function>
</package>