]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/wikilens/RatingsUser.php
Use RatingsDb singleton.
[SourceForge/phpwiki.git] / lib / wikilens / RatingsUser.php
1 <?php //-*-php-*-
2 rcs_id('$Id: RatingsUser.php,v 1.3 2004-06-30 20:06:44 dfrankow Exp $');
3 /* Copyright (C) 2004 Dan Frankowski
4  *
5  * This file is (not yet) part of PhpWiki.
6  * 
7  * PhpWiki is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  * 
12  * PhpWiki is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with PhpWiki; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 require_once("lib/wikilens/RatingsDb.php");
23
24 /**
25  * Get a RatingsUser instance (possibly from a cache).
26  */
27 class RatingsUserFactory {
28     function & getUser($userid) {
29         //print "getUser($userid) ";
30         global $_ratingsUserCache;
31         if (!isset($_ratingsUserCache)) {
32             $_ratingsUserCache = array();
33         }
34         if (!array_key_exists($userid, $_ratingsUserCache)) {
35             //print "MISS ";
36             $_ratingsUserCache[$userid] = new RatingsUser($userid);
37         }
38         else {
39             //print "HIT ";
40         }
41         return $_ratingsUserCache[$userid];
42     }
43 }
44
45 /**
46  * This class represents a user that gets ratings
47  */
48 class RatingsUser {
49     var $_userid;
50     var $_ratings_loaded;
51     var $_ratings;
52     var $_num_ratings;
53     var $_mean_ratings;
54     var $_pearson_sims;
55
56     function RatingsUser($userid) {
57         $this->_userid = $userid;
58         $this->_ratings_loaded = false;
59         $this->_ratings = array();
60         $this->_num_ratings = 0;
61         $this->_mean_ratings = array();
62         $this->_pearson_sims = array();
63     }
64
65     function getId() {
66         return $this->_userid;
67     }
68
69     function _get_rating_dbi() {
70         // This is a hack, because otherwise this object doesn't know about a
71         // DBI at all.  Perhaps all this ratings stuff should live somewhere
72         // else that's less of a base class.
73         if (isset($this->_rdbi))
74             return $this->_rdbi;
75         $this->_rdbi = RatingsDb::getTheRatingsDb();
76         return $this->_rdbi;
77     }
78
79     // XXX: may want to think about caching ratings in the PHP session 
80     // since a WikiUser is created for *every* access, in which case rate.php
81     // will want to change to use this object instead of direct db access
82
83     /**
84      * Check whether $user is allowed to view this user's ratings
85      * 
86      * @return bool True if $user can view this user's ratings, false otherwise
87      */
88     function allow_view_ratings($user)
89     {
90         return true;
91     }
92
93     /**
94      * Gets this user's ratings
95      * 
96      * @return array Assoc. array [page_name][dimension] = _UserRating object
97      */
98     function get_ratings()
99     {
100         $this->_load_ratings();
101         return $this->_ratings;
102     }
103
104     /**
105     * Gets this user's mean rating across a dimension
106     * 
107     * @return float Mean rating
108     */
109     function mean_rating($dimension = 0)
110     {
111         // use memoized result if available
112         if (isset($this->_mean_ratings[$dimension])) 
113         {
114             return $this->_mean_ratings[$dimension];
115         }
116
117         $ratings = $this->get_ratings();
118         $total = 0;
119         $n = 0;
120
121         // walk the ratings and aggregate those in this dimension
122         foreach($ratings as $page => $rating)
123         {
124             if (isset($rating[$dimension]))
125             {
126                 $total += $rating[$dimension]->get_rating();
127                 $n++;
128             }
129         }
130
131         // memoize and return result
132         $this->_mean_ratings[$dimension] = ($n == 0 ? 0 : $total / $n);
133         return $this->_mean_ratings[$dimension];
134     }
135
136     // Note: the following has_rated, get_rating, set_rating, and unset_rating
137     // methods are colossally inefficient as they do a full ratings load from
138     // the database before performing their intended operation -- as such, the
139     // rate.php script still uses the direct database methods (plus, it's very
140     // ephemeral and doesn't particularly care about the ratings count or any
141     // other features that these methods might provide down the road)
142
143     function has_rated($pagename, $dimension = null)
144     {
145         // XXX: does this really want to do a full ratings load?  (scalability?)
146         $this->_load_ratings();
147         if (isset($dimension))
148         {
149             
150
151             if (isset($this->_ratings[$pagename][$dimension]))
152             {
153                 return true;
154             }
155         }
156         else
157         {
158             if (isset($this->_ratings[$pagename]))
159             {
160                 return true;
161             }
162         }
163         return false;
164     }
165
166     function get_rating($pagename, $dimension = 0)
167     {
168         // XXX: does this really want to do a full ratings load?  (scalability?)
169         $this->_load_ratings();
170
171
172         if ($this->has_rated($pagename, $dimension))
173         {
174
175             
176             return $this->_ratings[$pagename][$dimension]->get_rating();
177         }
178         return false;
179     }
180
181     function set_rating($pagename, $dimension, $rating)
182     {
183         // XXX: does this really want to do a full ratings load?  (scalability?)
184         $this->_load_ratings();
185
186         // XXX: what to do on failure?
187         $dbi = $this->_get_rating_dbi();
188         if (!($dbi->rate($this->_userid, $pagename, $dimension, $rating)))
189         {
190             return;
191         }
192
193         if ($this->has_rated($pagename, $dimension))
194         {
195             $this->_ratings[$pagename][$dimension]->set_rating($rating);
196         }
197         else
198         {
199             $this->_num_ratings++;
200             $this->_ratings[$rating['pagename']][$rating['dimension']]
201               = new _UserRating($this->_userid, $pagename, $dimension, $rating);
202         }
203     }
204
205     function unset_rating($pagename, $dimension)
206     {
207         // XXX: does this really want to do a full ratings load?  (scalability?)
208         $this->_load_ratings();
209         if ($this->has_rated($pagename, $dimension))
210         {
211             // XXX: what to do on failure?
212             if ($this->_dbi->delete_rating($this->_userid,$pagename,$dimension))
213             {
214                 $this->_num_ratings--;
215                 unset($this->_ratings[$pagename][$dimension]);
216                 if (!count($this->_ratings[$pagename]))
217                 {
218                     unset($this->_ratings[$pagename]);
219                 }
220             }
221         }
222     }
223
224     function pearson_similarity($user, $dimension = 0)
225     {
226         // use memoized result if available
227         if (isset($this->_pearson_sims[$user->getId()][$dimension]))
228         {
229             return $this->_pearson_sims[$user->getId()][$dimension];
230         }
231     
232         $ratings1 = $this->get_ratings();
233         $mean1    = $this->mean_rating($dimension);
234         // XXX: sanify user input?
235         $ratings2 = $user->get_ratings();
236         $mean2    = $user->mean_rating($dimension);
237
238         // swap if it would speed things up a bit
239         if (count($ratings1) < count($ratings2))
240         {
241             $tmp = $ratings1;
242             $ratings1 = $ratings2;
243             $ratings2 = $tmp;
244             $tmp = $mean1;
245             $mean1 = $mean2;
246             $mean2 = $tmp;
247         }
248
249         list($sum11, $sum22, $sum12, $n) = array(0,0,0,0);
250
251         // compute sum(x*x), sum(y*y), sum(x*y) over co-rated items
252         foreach ($ratings1 as $page => $rating1)
253         {
254             if (isset($rating1[$dimension]) && isset($ratings2[$page]))
255             {
256                 $rating2 = $ratings2[$page];
257                 if (isset($rating2[$dimension]))
258                 {
259                     $r1 = $rating1[$dimension]->get_rating();
260                     $r2 = $rating2[$dimension]->get_rating();
261                     // print "co-rating with " . $user->getId() . " $page $r1 $r2<BR>";
262
263                     $r1 -= $mean1;
264                     $r2 -= $mean2;
265
266                     $sum11 += $r1 * $r1;
267                     $sum22 += $r2 * $r2;
268                     $sum12 += $r1 * $r2;
269                     $n++;
270                 }
271             }
272         }
273
274         // this returns both the computed similarity and the number of co-rated
275         // items that the similarity was based on
276
277         // prevent division-by-zero
278         if (sqrt($sum11) == 0 || sqrt($sum12) == 0) 
279             $sim = array(0, $n);
280         else
281         // Pearson similarity
282             $sim = array($sum12 / (sqrt($sum11) * sqrt($sum22)), $n);
283
284         // print "sim is " . $sim[0] . "<BR><BR>";
285
286         // memoize result
287         $this->_pearson_sims[$user->getId()][$dimension] = $sim;
288         return $this->_pearson_sims[$user->getId()][$dimension] = $sim;
289     }
290
291     function knn_uu_predict($pagename, &$neighbors, $dimension = 0)
292     {
293         /*
294         print "<PRE>";
295         var_dump($this->_pearson_sims);
296         var_dump($this->_ratings);
297         print "</PRE>";
298         print "pred for $pagename<BR>";
299         */
300         $total = 0;
301         $total_sim = 0;
302
303         // foreach($neighbors as $nbor)
304         // {
305         for($i = 0; $i < count($neighbors); $i++)
306         {
307             // more silly PHP references...
308             $nbor =& $neighbors[$i];
309
310             // ignore self-neighbor
311             if ($this->getId() == $nbor->getId())
312                 continue;
313
314             if ($nbor->has_rated($pagename, $dimension))
315             {
316                 list($sim, $n_items) = $this->pearson_similarity($nbor);
317                 // ignore absolute sims below 0.1, negative sims??
318                 // XXX: no filtering done... small-world = too few neighbors
319                 if (1 || ($sim > 0 && abs($sim) >= 0.1))
320                 {
321                     // n/50 sig weighting
322                     if ($n_items < 50)
323                         $sim *= $n_items / 50;
324                     /*
325                     print "neighbor is " . $nbor->getId() . "<BR>";
326                     print "weighted sim is " . $sim . "<BR>";
327                     print "dev from mean is " . ($nbor->get_rating($pagename, $dimension) - $nbor->mean_rating($dimension)) . "<BR>";
328                     */
329                     $total += $sim * ($nbor->get_rating($pagename, $dimension) - $nbor->mean_rating($dimension));
330                     $total_sim += abs($sim);
331                 }
332             }
333         }
334
335         $my_mean = $this->mean_rating($dimension);
336         /*
337         print "your mean is $my_mean<BR>";
338         print "pred dev from mean is " . ($total_sim == 0 ? -1 : ($total / $total_sim)) . "<BR>";
339         print "pred is " . ($total_sim == 0 ? -1 : ($total / $total_sim + $my_mean)) . "<BR><BR>";
340         */
341         // XXX: what to do if no neighbors have rated pagename?
342         return ($total_sim == 0 ? 0 : ($total / $total_sim + $my_mean));
343     }
344
345     function _load_ratings($force = false)
346     {
347         if (!$this->_ratings_loaded || $force)
348         {
349             // print "load " . $this->getId() . "<BR>";
350             $this->_ratings = array();
351             $this->_num_ratings = 0;
352             // only signed-in users have ratings (XXX: authenticated?)
353
354             // passing null as first parameter to indicate all dimensions
355             $dbi = $this->_get_rating_dbi();
356
357             $rating_iter = $dbi->sql_get_rating(null, $this->_userid, null);
358             //$rating_iter = $dbi->get_rating(null, $this->_userid);
359
360             while($rating = $rating_iter->next())
361             {
362                 $this->_num_ratings++;
363                 $this->_ratings[$rating['pagename']][$rating['dimension']]
364                   = new _UserRating($this->_userid, 
365                                     $rating['pagename'],
366                                     $rating['dimension'], 
367                                     $rating['ratingvalue']);
368             }
369
370             $this->_ratings_loaded = true;
371         }
372     }
373 }
374
375 /** Represent a rating. */
376 class _UserRating
377 {
378     function _UserRating ($rater, $ratee, $dimension, $rating) 
379     {
380         $this->rater     = (string)$rater;
381         $this->ratee     = (string)$ratee;
382         $this->dimension = (int)$dimension;
383         $this->rating    = (float)$rating;
384     }
385
386     function get_rater() 
387     {
388         return $this->rater;
389     }
390
391     function get_ratee() 
392     {
393         return $this->ratee;
394     }
395
396     function get_rating() 
397     {
398         return $this->rating;
399     }
400
401     function get_dimension() 
402     {
403         return $this->dimension;
404     }
405
406     function set_rater() 
407     {
408         $this->rater = (string)$rater;
409     }
410
411     function set_ratee() 
412     {
413         $this->ratee = (string)$ratee;
414     }
415
416     function set_rating() 
417     {
418         $this->rating = (float)$rating;
419     }
420
421     function set_dimension() 
422     {
423         $this->dimension = (int)$dimension;
424     }
425 }
426
427 // $Log: not supported by cvs2svn $
428 // Revision 1.2  2004/06/21 17:01:41  rurban
429 // fix typo and rating method call
430 //
431 // Revision 1.1  2004/06/18 14:42:17  rurban
432 // added wikilens libs (not yet merged good enough, some work for DanFr)
433 //
434
435 // Local Variables:
436 // mode: php
437 // tab-width: 8
438 // c-basic-offset: 4
439 // c-hanging-comment-ender-p: nil
440 // indent-tabs-mode: nil
441 // End:
442 ?>