Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
blob: 96b251754e2f5fed4f3a8a130c3dd7ec9750f51b (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
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
  Copyright (C) 2015, 2018 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="Functions for performing table lookups">

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

  <!-- since templates are inlined, we need to make these symbols available to
       avoid terrible confusion -->
  <import package="../numeric/common" export="true"/>
  <import package="common" export="true" />
  <import package="filter" export="true" />
  <import package="matrix" export="true" />

  <!--
    Create a constant table

    This definition must appear within a `constants' block.

    Permitted children:
      - _table_column_+ - Column definitions
      - _table_rows_    - Begin table data definition
  -->
  <template name="_create-table_"
               desc="Create an arbitrary table for querying">
    <param name="@name@"   desc="Table name" />
    <param name="@values@" desc="Table definition" />

    <param name="@desc@" desc="Table description">
      <text></text>
    </param>

    <param name="@__tid@"
              desc="Internal table identifier">
      <param-value name="@name@" upper="true" snake="true" />
    </param>


    <param-copy name="@values@">
      <param-meta name="create-table-id" value="@__tid@" />
      <param-meta name="create-table-name" value="@name@" />
      <param-meta name="create-table-desc" value="@desc@" />
    </param-copy>
  </template>


  <!--
    Declare a table column name

    A column definition assigns a field name to a column index. If the
    column always contains ordered (sequenced) data, then @seq@ should
    be set to "true"; this allows a more efficient query strategy to
    be used.

    If a table is especially large, the first column should be treated
    as the index and always be sequenced.
  -->
  <template name="_table-column_"
               desc="Declare name for table column">
    <param name="@name@"  desc="Column name" />
    <param name="@index@" desc="Column index (0-indexed)" />

    <param name="@desc@" desc="Column description">
      <param-value name="@name@" />
    </param>

    <!-- use carefully; leave alone unless data is definately sorted,
         or query results may be incorrect -->
    <param name="@seq@" desc="Column is sorted (sequential)">
      <text></text>
    </param>

    <param name="@__tid@"
              desc="Internal table identifier">
      <param-inherit meta="create-table-id" />
    </param>

    <!-- FIXME: this doesn't contain @__tid@ because of a bug in the
         preprocessor; this is fixed in the new DSL -->
    <param name="@__constname@"
              desc="Name of internal constant used for column lookup">
      <text>RATE_TABLE_</text>
      <param-value name="@name@" upper="true" snake="true" />
    </param>


    <!-- column index identifier -->
    <const name="{@__tid@}_{@__constname@}"
              value="@index@" type="integer"
              desc="@desc@" />

    <!-- column sequential flag to permit query optimizations -->
    <if name="@seq@" eq="true">
      <const name="{@__tid@}_{@__constname@}_IS_SEQ"
                value="1" type="integer"
                desc="{@name@} is sequenced" />
    </if>
    <unless name="@seq@" eq="true">
      <const name="{@__tid@}_{@__constname@}_IS_SEQ"
                value="0" type="integer"
                desc="{@name@} is unordered" />
    </unless>
  </template>


  <!--
    Begin table data definition (rows)

    Each _table-row_ child inserts a constant row into the table. Note
    that all data must be constant.

    Use only one of @data@ or children to define rows.

    Permitted children:
      - _table-row_* - Table row definitions
  -->
  <template name="_table-rows_"
            desc="Begin table data definition">
    <param name="@values@" desc="Row definitions" />
    <param name="@data@"   desc="GNU Octave/MATLAB-style data definition" />

    <param name="@__tid@"
           desc="Table identifier">
      <param-inherit meta="create-table-id" />
    </param>

    <param name="@__tname@"
           desc="Table name as provided by caller">
      <param-inherit meta="create-table-name" />
    </param>

    <param name="@__desc@"
           desc="Table description">
      <param-inherit meta="create-table-desc" />
    </param>


    <if name="@data@">
      <const name="{@__tid@}_RATE_TABLE"
             type="float"
             desc="{@__tname@} table; {@__desc@}"
             values="@data@" />
    </if>
    <unless name="@data@">
      <const name="{@__tid@}_RATE_TABLE"
             type="float"
             desc="{@__tname@} table; {@__desc@}">
        <param-copy name="@values@" />
      </const>
    </unless>
  </template>


  <!--
    Define a constant table row

    Rows will be inserted into the table in the order in which they
    appear. Note that, if the column is marked as sequenced, it is
    important that the order is itself sequenced.

    All values must be constant.

    Permitted children:
      _table-value_+ - Row column value
  -->
  <template name="_table-row_"
               desc="Define a constant table row (ordered)">
    <param name="@values@" desc="Column values" />


    <set desc="Row">
      <param-copy name="@values@" />
    </set>
  </template>


  <!--
    Set a column value for the parent row

    Column value order should match the defined column order. All
    values must be constants.
  -->
  <template name="_table-value_"
               desc="Table column value for row (ordered)">
    <param name="@const@" desc="Constant column value" />


    <item value="@const@" desc="Column value" />
  </template>


  <template name="_query-first-field_" desc="Return the requested field from the first row returned by a query">
    <param name="@table@" desc="Table (matrix) to query" />
    <param name="@values@" desc="Query parameters" />

    <!-- use either field or name[+index] -->
    <param name="@field@" desc="Column to select (variable/constant); do not use with @name@" />
    <param name="@name@" desc="Name of field to query (variable/constant); overrides @field@" />
    <param name="@index@" desc="Optional index for field name lookup (using @name@)" />


    <c:car label="First row of query result">
      <t:query-field table="@table@" field="@field@" name="@name@" index="@index@">
        <param-copy name="@values@" />
      </t:query-field>
    </c:car>
  </template>


  <!--
    Query-style syntax for matrix searches

    Overhead of this algorithm is minimal. Let us consider that a direct access
    to an element in a vector is O(1). Therefore, given a set of p predicates
    in a direct matrix access, the time would be O(p).

    In the case of this query method, we perform first a bisect and then a
    linear search backward to the first element matching the given predicate.
    Therefore, per field, the best case average is O(lg n), where n is the
    number of rows; this assumes that no backward linear search is required.
    Should such a search be required, the worst case per field is O(lg n + m),
    where m is the length of the largest subset.

    Once again considering p predicates, each predicate P_k where k > 0
    (0-indexed) will operate on a subset of the first predicate. As already
    discussed, the worst-case scenerio for the length of the subset is m. Since
    further queries on this subset are rarely likely to use the bisect
    algorithm, we're looking at O(m) time per field. Therefore, the total query
    time for the best-case scenerio is still O(lg n) if m is sufficiently small
    and O(lg n + pm) if m is sufficiently large.

    An important case to note is when m approaches (or is equal to) n; in such
    a case, the algorithm degrades to a worst-case linear search of O(pn) and
    best case of O(n) if early predicates are sufficiently effective at
    reducing the subsets to further predicates: That is, the bisect algorithm
    either cannot be used or is ineffective. For large m, this may cause a
    stack overflow due to the recursive nature of the algorithm. Is is
    therefore important to order the first column of the table such that it is
    both sorted and produces small m.  Additionally, it is ideal for the first
    predicate to query the first field to quickly reduce the size of the set
    for the next predicate.
  -->
  <template name="_query-field_" desc="Return the requested field from rows returned by a query">
    <param name="@table@" desc="Table (matrix) to query" />
    <param name="@values@" desc="Query parameters" />

    <!-- use one or the other -->
    <param name="@field@" desc="Column to select (variable/constant); do not use with @name@" />

    <param name="@index@" desc="Optional index for field name lookup (using @name@)" />
    <!-- by default, if @field@ is provided instead of @name@, the field
         constant will be generated (same concept as the 'when' template) -->
    <param name="@name@" desc="Name of field to query (variable/constant); overrides @field@">
      <!-- convert @table@ to uppercase and snake case (replace - with _) -->
      <param-value name="@table@" upper="true" snake="true" />
      <text>_RATE_TABLE_</text>
      <param-value name="@field@" upper="true" snake="true" />
    </param>


    <c:apply name="mcol" label="Query result">
      <!-- the matrix (vector of rows) returned by the query -->
      <c:arg name="matrix">
        <t:query-row table="@table@">
          <param-copy name="@values@" />
        </t:query-row>
      </c:arg>

      <!-- the field (column) to retrieve; 0-based index -->
      <c:arg name="col">
        <!-- no index lookup needed -->
        <unless name="@index@">
          <c:value-of name="@name@" />
        </unless>

        <!-- index lookup required -->
        <if name="@index@">
          <c:value-of name="@name@" index="@index@" />
        </if>
      </c:arg>
    </c:apply>
  </template>


  <template name="_query-row_" desc="Query a table (matrix) for a row (vector) of values">
    <param name="@table@" desc="Table (matrix)" />
    <param name="@values@" desc="Query parameters" />

    <!-- this defaults to a table name constant as generated from the csv2xml
         script; either this or @table@ should be used -->
    <param name="@matrix@" desc="Matrix to look up from">
      <!-- convert @table@ to uppercase and snake case (replace - with _) -->
      <param-value name="@table@" upper="true" snake="true" />
      <text>_RATE_TABLE</text>
    </param>

    <c:apply name="_mquery">
      <c:arg name="matrix">
        <c:value-of name="@matrix@" />
      </c:arg>

      <c:arg name="criteria">
        <c:vector>
          <param-copy name="@values@">
            <param-meta name="table_basename" value="@matrix@" />
          </param-copy>
        </c:vector>
      </c:arg>

      <c:arg name="i">
        <!-- begin with the last predicate (due to the way we'll recurse, it
             will be applied *last* -->
        <t:dec>
          <c:length-of>
            <c:vector>
              <param-copy name="@values@">
                <param-meta name="table_basename" value="@matrix@" />
              </param-copy>
            </c:vector>
          </c:length-of>
        </t:dec>
      </c:arg>
    </c:apply>
  </template>


  <template name="_when_" desc="Create field predicate for query definition">
    <param name="@id@" desc="Field index" />
    <param name="@values@" desc="Field value (provide only one node)" />
    <param name="@sequential@" desc="Is data sequential?" />

    <!-- @name@ may be provided directly, or @field@ may be used when the
         basename is available (set by a query template), giving the illusion of
         querying the table columns by name directly (magic!); pure syntatic
         sugary goodness -->
    <param name="@field@" desc="Field name (to be used with base)" />
    <param name="@name@" desc="Field name (as a variable/constant)">
      <param-inherit meta="table_basename" />
      <text>_</text>
      <param-value name="@field@" upper="true" snake="true" />
    </param>

    <param name="@seqvar@" desc="Var/constant containing whether field is sequential">
      <param-inherit meta="table_basename" />
      <text>_</text>
      <param-value name="@field@" upper="true" snake="true" />
      <text>_IS_SEQ</text>
    </param>

    <c:vector label="Conditional for {@field@}">
      <!-- the first element will represent the column (field) index -->
      <unless name="@name@">
        <c:const value="@id@" type="integer" desc="Field index" />
      </unless>
      <if name="@name@">
        <c:value-of name="@name@" />
      </if>

      <!-- the second element will represent the expected value(s) -->
      <c:vector>
        <param-copy name="@values@" />
      </c:vector>

      <!-- the final element will represent whether or not this field is sequential -->
      <if name="@sequential@">
        <c:const value="@sequential@" type="boolean" desc="Whether data is sequential" />
      </if>
      <unless name="@sequential@">
        <!-- if a field name was given, we can get the sequential information
             that was already generated for us -->
        <if name="@field@">
          <c:value-of name="@seqvar@" />
        </if>
        <!-- otherwise, default to non-sequential -->
        <unless name="@field@">
          <c:value-of name="FALSE" />
        </unless>
      </unless>
    </c:vector>
  </template>



  <!--
    These functions make the magic happen

    They are hideous. Look away.
  -->


  <!-- this function is intended to be called by the _query_ template, not directly -->
  <function name="_mquery" desc="Query for vectors using a set of column criteria">
    <param name="matrix" type="float" set="matrix" desc="Matrix to query" />
    <param name="criteria" type="float" set="matrix" desc="Query criteria" />

    <param name="i" type="integer" desc="Current criteria index" />

    <c:cases>
      <c:case>
        <c:when name="i">
          <c:eq>
            <!-- it's important that we allow index 0, since that is a valid
                 predicate -->
            <c:const value="-1" type="integer" desc="We're done." />
          </c:eq>
        </c:when>

        <!-- we're done; stick with the result -->
        <c:value-of name="matrix" />
      </c:case>

      <c:otherwise>
        <c:apply name="mfilter">
          <!-- matrix to search -->
          <c:arg name="matrix">
            <!-- >> recursion happens here << -->
            <c:apply name="_mquery">
              <c:arg name="matrix">
                <c:value-of name="matrix" />
              </c:arg>

              <c:arg name="criteria">
                <c:value-of name="criteria" />
              </c:arg>

              <c:arg name="i">
                <t:dec>
                  <c:value-of name="i" />
                </t:dec>
              </c:arg>
            </c:apply>
          </c:arg>

          <!-- field (column) -->
          <c:arg name="col">
            <c:value-of name="criteria">
              <c:index>
                <c:value-of name="i" />
              </c:index>
              <c:index>
                <c:const value="0" type="integer" desc="Field id" />
              </c:index>
            </c:value-of>
          </c:arg>

          <!-- value(s) to search for -->
          <c:arg name="vals">
            <c:value-of name="criteria">
              <c:index>
                <c:value-of name="i" />
              </c:index>
              <c:index>
                <c:const value="1" type="integer" desc="Field value" />
              </c:index>
            </c:value-of>
          </c:arg>

          <!-- if it's sequential, we can cut down on the search substantially -->
          <c:arg name="seq">
            <c:value-of name="criteria">
              <c:index>
                <c:value-of name="i" />
              </c:index>
              <c:index>
                <c:const value="2" type="integer" desc="Sequential flag" />
              </c:index>
            </c:value-of>
          </c:arg>
        </c:apply>
      </c:otherwise>
    </c:cases>
  </function>
</package>