~ubuntu-branches/debian/sid/apophenia/sid

« back to all changes in this revision

Viewing changes to apop_linear_constraint.c

  • Committer: Package Import Robot
  • Author(s): Jerome Benoit
  • Date: 2014-09-11 12:36:28 UTC
  • Revision ID: package-import@ubuntu.com-20140911123628-mhyqci1xk9tjicph
Tags: upstream-0.999b+ds1
ImportĀ upstreamĀ versionĀ 0.999b+ds1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 
 
2
/** \file apop_linear_constraint.c 
 
3
  \c apop_linear_constraint finds a point that meets a set of linear constraints. This takes a lot of machinery, so it gets its own file.
 
4
 
 
5
Copyright (c) 2007, 2009 by Ben Klemens.  Licensed under the modified GNU GPL v2; see COPYING and COPYING2.  
 
6
*/
 
7
#include "apop_internal.h"
 
8
 
 
9
static double magnitude(gsl_vector *v){
 
10
    double out;
 
11
    gsl_blas_ddot(v, v, &out);
 
12
    return out;
 
13
}
 
14
 
 
15
static void find_nearest_point(gsl_vector *V, double k, gsl_vector *B, gsl_vector *out){
 
16
    /* Find X such that BX =K and there is an S such that X + SB=V. */
 
17
    double S=0; //S = (BV-K)/B'B.
 
18
    gsl_blas_ddot(B, V, &S);
 
19
    S   -= k;
 
20
assert(!gsl_isnan(S));
 
21
    S   /= magnitude(B);
 
22
assert(!gsl_isnan(S));
 
23
    gsl_vector_memcpy(out, B); //X = -SB +V
 
24
    gsl_vector_scale(out, -S);
 
25
    gsl_vector_add(out, V);
 
26
assert(!gsl_isnan(gsl_vector_get(out,0)));
 
27
}
 
28
 
 
29
static int binds(gsl_vector const *v, double k, gsl_vector const *b, double margin){
 
30
    double d;
 
31
    gsl_blas_ddot(v, b, &d);
 
32
    return d < k + margin;
 
33
}
 
34
 
 
35
static double trig_bit(gsl_vector *dimv, gsl_vector *otherv, double off_by){
 
36
    double theta, costheta, dot, out;
 
37
    gsl_blas_ddot(dimv, otherv, &dot);
 
38
    costheta = dot/(magnitude(dimv)*magnitude(otherv));
 
39
    theta = acos(costheta);
 
40
    out = off_by/gsl_pow_2(sin(theta)); 
 
41
    return out;
 
42
}
 
43
 
 
44
/* The hard part is when your candidate point does not satisfy other
 
45
   constraints, so you need to translate the point until it meets the new hypersurface.
 
46
   How far is that? Project beta onto the new surface, and find the
 
47
   distance between that projection and the original surface. Then
 
48
   translate beta toward the original surface by that amount. The
 
49
   projection of the translated beta onto the new surface now also touches the old
 
50
   surface.
 
51
   */
 
52
static void get_candiate(gsl_vector *beta, apop_data *constraint, int current, gsl_vector *candidate, double margin){
 
53
    double k, ck, off_by, s;
 
54
    gsl_vector *pseudobeta        = NULL;
 
55
    gsl_vector *pseudocandidate   = NULL;
 
56
    gsl_vector *pseudocandidate2  = NULL;
 
57
    gsl_vector *fix               = NULL;
 
58
    gsl_vector *cc = Apop_rv(constraint, current);
 
59
    ck = gsl_vector_get(constraint->vector, current);
 
60
    find_nearest_point(beta, ck, cc, candidate);
 
61
    for (size_t i=0; i< constraint->vector->size; i++){
 
62
        if (i!=current){
 
63
            gsl_vector *other = Apop_rv(constraint, i);
 
64
            k   =apop_data_get(constraint, i, -1);
 
65
            if (binds(candidate, k, other, margin)){
 
66
                if (!pseudobeta){
 
67
                    pseudobeta          = gsl_vector_alloc(beta->size);
 
68
                    gsl_vector_memcpy(pseudobeta, beta);
 
69
                    pseudocandidate     = gsl_vector_alloc(beta->size);
 
70
                    pseudocandidate2    = gsl_vector_alloc(beta->size);
 
71
                    fix                 = gsl_vector_alloc(beta->size);
 
72
                }
 
73
                find_nearest_point(pseudobeta, k, other, pseudocandidate);
 
74
                find_nearest_point(pseudocandidate, ck, cc, pseudocandidate2);
 
75
                off_by  = apop_vector_distance(pseudocandidate, pseudocandidate2);
 
76
                s       = trig_bit(cc, other, off_by);
 
77
                gsl_vector_memcpy(fix, cc);
 
78
                gsl_vector_scale(fix, magnitude(cc));
 
79
                gsl_vector_scale(fix, s);
 
80
                gsl_vector_add(pseudobeta, fix);
 
81
                find_nearest_point(pseudobeta, k, other, candidate);
 
82
                gsl_vector_memcpy(pseudobeta, candidate);
 
83
            } 
 
84
        }
 
85
    }
 
86
    if (fix){ 
 
87
        gsl_vector_free(fix); gsl_vector_free(pseudobeta);
 
88
        gsl_vector_free(pseudocandidate); gsl_vector_free(pseudocandidate2);
 
89
    }
 
90
}
 
91
 
 
92
/** This is designed to be called from within the constraint method of your \ref
 
93
apop_model. Just write the constraint vector+matrix and this will do the rest.
 
94
See the outline page for detailed discussion on setting contrasts. 
 
95
 
 
96
\param beta    The proposed vector about to be tested. No default, must not be \c NULL.
 
97
 
 
98
\param constraint  
 
99
A vector/matrix pair [v | m1 m2 ... mn] where each row is interpreted as a less-than inequality:
 
100
\f$v < m1x1+ m2x2 + ... + mnxn\f$.  For example, say your constraints are 
 
101
\f$3 < 2x + 4y - 7z\f$ and \f$y\f$ is positive, i.e. \f$0 < y\f$.
 
102
Allocate and fill the matrix representing these two constraints via:
 
103
\code
 
104
apop_data *constr = apop_data_falloc((2,2,3), 3,  2, 4, 7,
 
105
                                              0,  0, 1, 0);
 
106
\endcode
 
107
. Default: each elements is greater than zero. E.g., for three parameters:
 
108
\code
 
109
apop_data *constr = apop_data_falloc((3,3,3), 0,  1, 0, 0,
 
110
                                              0,  0, 1, 0,
 
111
                                              0,  0, 0, 1);
 
112
\endcode
 
113
 
 
114
\param margin If zero, then this is a >= constraint, otherwise I will return a point this amount within the borders. You could try \c GSL_DBL_EPSILON, which is the smallest value a \c double can hold, or something like 1e-3. Default = 0.
 
115
 
 
116
return The penalty = the distance between beta and the closest point that meets the constraints.
 
117
If the constraint is not met, this \c beta is shifted by \c margin (Euclidean distance) to meet the constraints. 
 
118
 
 
119
\li If your \ref apop_data is not just a vector, try \ref apop_data_pack to pack it into a vector. This is what \ref apop_maximum_likelihood does.
 
120
 
 
121
\li This function uses the \ref designated syntax for inputs.
 
122
todo The apop_linear_constraint function doesn't check for odd cases like coplanar constraints.
 
123
*/
 
124
#ifdef APOP_NO_VARIADIC
 
125
long double apop_linear_constraint(gsl_vector *beta, apop_data * constraint, double margin){
 
126
#else
 
127
apop_varad_head(long double, apop_linear_constraint){
 
128
    static threadlocal apop_data *default_constraint;
 
129
    gsl_vector * apop_varad_var(beta, NULL);
 
130
    double apop_varad_var(margin, 0);
 
131
    apop_data * apop_varad_var(constraint, NULL);
 
132
    Apop_assert(beta, "The vector to be checked is NULL.");
 
133
    if (!constraint){
 
134
        if (default_constraint && beta->size != default_constraint->vector->size){
 
135
            apop_data_free(default_constraint);
 
136
            default_constraint = NULL;
 
137
        }
 
138
        if (!default_constraint){
 
139
            default_constraint = apop_data_alloc(0,beta->size, beta->size);
 
140
            default_constraint->vector = gsl_vector_calloc(beta->size);
 
141
            gsl_matrix_set_identity(default_constraint->matrix);
 
142
        }
 
143
        constraint = default_constraint;
 
144
    }
 
145
    return apop_linear_constraint_base(beta, constraint, margin);
 
146
}
 
147
 
 
148
 long double apop_linear_constraint_base(gsl_vector *beta, apop_data * constraint, double margin){
 
149
#endif
 
150
    static threadlocal gsl_vector *closest_pt = NULL;
 
151
    static threadlocal gsl_vector *candidate  = NULL;
 
152
    static threadlocal gsl_vector *fix        = NULL;
 
153
    int constraint_ct = constraint->matrix->size1;
 
154
    int bindlist[constraint_ct];
 
155
    int i, bound = 0;
 
156
    /* For added efficiency, keep a scratch vector or two on hand. */
 
157
    if (closest_pt==NULL || closest_pt->size != constraint->matrix->size2){
 
158
        closest_pt  = gsl_vector_calloc(beta->size);
 
159
        candidate   = gsl_vector_alloc(beta->size);
 
160
        fix         = gsl_vector_alloc(beta->size);
 
161
        closest_pt->data[0] = GSL_NEGINF;
 
162
    }
 
163
    /* Do any constraints bind?*/
 
164
    memset(bindlist, 0, sizeof(int)*constraint_ct);
 
165
    for (i=0; i< constraint_ct; i++){
 
166
        gsl_vector *c = Apop_rv(constraint, i);
 
167
        bound       +=
 
168
        bindlist[i] = binds(beta, apop_data_get(constraint, i, -1), c, margin);
 
169
    }
 
170
    if (!bound) return 0;   //All constraints met.
 
171
    gsl_vector *base_beta = apop_vector_copy(beta);
 
172
    /* With only one constraint, it's easy. */
 
173
    if (constraint->vector->size==1){
 
174
        gsl_vector *c = Apop_rv(constraint, 0);
 
175
        find_nearest_point(base_beta, constraint->vector->data[0], c, beta);
 
176
        goto add_margin;
 
177
    }
 
178
    /* Finally, multiple constraints, at least one binding.
 
179
       For each surface, pick a candidate point.
 
180
       Check whether the point meets the other constraints. 
 
181
            if not, translate to a new point that works.
 
182
            [Do this by maintaining a pseudopoint that translates by the
 
183
            necessary amount.]
 
184
        Once you have a candidate point, compare its distance to the
 
185
        current favorite; keep the best.
 
186
     */
 
187
    for (i=0; i< constraint_ct; i++)
 
188
        if (bindlist[i]){
 
189
            get_candiate(base_beta, constraint, i, candidate, margin);
 
190
            if(apop_vector_distance(base_beta, candidate) < apop_vector_distance(base_beta, closest_pt))
 
191
                gsl_vector_memcpy(closest_pt, candidate);
 
192
        }
 
193
    gsl_vector_memcpy(beta, closest_pt);
 
194
add_margin:
 
195
    for (i=0; i< constraint_ct; i++){
 
196
        if(bindlist[i]){
 
197
            gsl_vector_memcpy(fix, Apop_rv(constraint, i));
 
198
            gsl_vector_scale(fix, magnitude(fix));
 
199
            gsl_vector_scale(fix, margin);
 
200
            gsl_vector_add(beta, fix);
 
201
        }
 
202
    }
 
203
    long double out = apop_vector_distance(base_beta, beta);
 
204
    gsl_vector_free(base_beta);
 
205
    return out;
 
206
}