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.
5
Copyright (c) 2007, 2009 by Ben Klemens. Licensed under the modified GNU GPL v2; see COPYING and COPYING2.
7
#include "apop_internal.h"
9
static double magnitude(gsl_vector *v){
11
gsl_blas_ddot(v, v, &out);
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);
20
assert(!gsl_isnan(S));
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)));
29
static int binds(gsl_vector const *v, double k, gsl_vector const *b, double margin){
31
gsl_blas_ddot(v, b, &d);
32
return d < k + margin;
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));
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
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++){
63
gsl_vector *other = Apop_rv(constraint, i);
64
k =apop_data_get(constraint, i, -1);
65
if (binds(candidate, k, other, margin)){
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);
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);
87
gsl_vector_free(fix); gsl_vector_free(pseudobeta);
88
gsl_vector_free(pseudocandidate); gsl_vector_free(pseudocandidate2);
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.
96
\param beta The proposed vector about to be tested. No default, must not be \c NULL.
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:
104
apop_data *constr = apop_data_falloc((2,2,3), 3, 2, 4, 7,
107
. Default: each elements is greater than zero. E.g., for three parameters:
109
apop_data *constr = apop_data_falloc((3,3,3), 0, 1, 0, 0,
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.
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.
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.
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.
124
#ifdef APOP_NO_VARIADIC
125
long double apop_linear_constraint(gsl_vector *beta, apop_data * constraint, double margin){
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.");
134
if (default_constraint && beta->size != default_constraint->vector->size){
135
apop_data_free(default_constraint);
136
default_constraint = NULL;
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);
143
constraint = default_constraint;
145
return apop_linear_constraint_base(beta, constraint, margin);
148
long double apop_linear_constraint_base(gsl_vector *beta, apop_data * constraint, double margin){
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];
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;
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);
168
bindlist[i] = binds(beta, apop_data_get(constraint, i, -1), c, margin);
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);
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
184
Once you have a candidate point, compare its distance to the
185
current favorite; keep the best.
187
for (i=0; i< constraint_ct; 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);
193
gsl_vector_memcpy(beta, closest_pt);
195
for (i=0; i< constraint_ct; 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);
203
long double out = apop_vector_distance(base_beta, beta);
204
gsl_vector_free(base_beta);