00001 /* 00002 ------------------------------------------------------------------- 00003 00004 Copyright (C) 2006, 2007, 2008, Andrew W. Steiner 00005 00006 This file is part of O2scl. 00007 00008 O2scl is free software; you can redistribute it and/or modify 00009 it under the terms of the GNU General Public License as published by 00010 the Free Software Foundation; either version 3 of the License, or 00011 (at your option) any later version. 00012 00013 O2scl is distributed in the hope that it will be useful, 00014 but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00016 GNU General Public License for more details. 00017 00018 You should have received a copy of the GNU General Public License 00019 along with O2scl. If not, see <http://www.gnu.org/licenses/>. 00020 00021 ------------------------------------------------------------------- 00022 */ 00023 #ifndef O2SCL_GSL_MISER_H 00024 #define O2SCL_GSL_MISER_H 00025 00026 #include <iostream> 00027 #include <o2scl/collection.h> 00028 #include <o2scl/mcarlo_inte.h> 00029 #include <o2scl/gsl_rnga.h> 00030 #include <gsl/gsl_math.h> 00031 #include <gsl/gsl_monte.h> 00032 #include <gsl/gsl_machine.h> 00033 #include <gsl/gsl_monte_miser.h> 00034 00035 #ifndef DOXYGENP 00036 namespace o2scl { 00037 #endif 00038 00039 /** 00040 \brief Multidimensional integration using Miser Miser Carlo (GSL) 00041 00042 \todo Document the fact that min_calls and min_calls_per_bi 00043 need to be set beforehand 00044 */ 00045 template<class param_t, class func_t=multi_funct<param_t>, 00046 class rng_t=gsl_rnga, class vec_t=ovector_view, class alloc_vec_t=ovector, 00047 class alloc_t=ovector_alloc> class gsl_miser : 00048 public mcarlo_inte<param_t,func_t,rng_t,vec_t> { 00049 00050 public: 00051 00052 /** 00053 \brief Introduce random variation into bisection (default 0.0) 00054 00055 From GSL documentation: 00056 \verbatim 00057 This parameter introduces a random fractional variation of size 00058 DITHER into each bisection, which can be used to break the 00059 symmetry of integrands which are concentrated near the exact 00060 center of the hypercubic integration region. The default value of 00061 dither is zero, so no variation is introduced. If needed, a 00062 typical value of DITHER is 0.1. 00063 \endverbatim 00064 */ 00065 double dither; 00066 00067 /** 00068 \brief Specify fraction of function calls for estimating variance 00069 00070 From GSL documentation: 00071 \verbatim 00072 This parameter specifies the fraction of the currently available 00073 number of function calls which are allocated to estimating the 00074 variance at each recursive step. The default value is 0.1. 00075 \endverbatim 00076 */ 00077 double estimate_frac; 00078 00079 /** 00080 \brief How estimated variances for two sub-regions are combined 00081 00082 From GSL documentation: 00083 \verbatim 00084 This parameter controls how the estimated variances for the two 00085 sub-regions of a bisection are combined when allocating points. 00086 With recursive sampling the overall variance should scale better 00087 than 1/N, since the values from the sub-regions will be obtained 00088 using a procedure which explicitly minimizes their variance. To 00089 accommodate this behavior the MISER algorithm allows the total 00090 variance to depend on a scaling parameter \alpha, 00091 00092 \Var(f) = {\sigma_a \over N_a^\alpha} + {\sigma_b \over N_b^\alpha}. 00093 00094 The authors of the original paper describing MISER recommend the 00095 value \alpha = 2 as a good choice, obtained from numerical 00096 experiments, and this is used as the default value in this 00097 implementation. 00098 \endverbatim 00099 */ 00100 double alpha; 00101 00102 /** 00103 \brief Minimum number of calls to estimate the variance 00104 (default 100) 00105 00106 From GSL documentation: 00107 \verbatim 00108 This parameter specifies the minimum number of function calls 00109 required for each estimate of the variance. If the number of 00110 function calls allocated to the estimate using ESTIMATE_FRAC falls 00111 below MIN_CALLS then MIN_CALLS are used instead. This ensures 00112 that each estimate maintains a reasonable level of accuracy. The 00113 default value of MIN_CALLS is `16 * dim'. 00114 \endverbatim 00115 */ 00116 size_t min_calls; 00117 00118 /** 00119 \brief Minimum number of calls required to proceed with bisection 00120 (default 4000) 00121 00122 From GSL documentation: 00123 \verbatim 00124 This parameter specifies the minimum number of function calls 00125 required to proceed with a bisection step. When a recursive step 00126 has fewer calls available than MIN_CALLS_PER_BISECTION it performs 00127 a plain Monte Carlo estimate of the current sub-region and 00128 terminates its branch of the recursion. The default value of this 00129 parameter is `32 * min_calls'. 00130 \endverbatim 00131 */ 00132 size_t min_calls_per_bisection; 00133 00134 #ifndef DOXYGEN_INTERNAL 00135 00136 protected: 00137 00138 /// Desc 00139 size_t dim; 00140 /// Desc 00141 int estimate_style; 00142 /// Desc 00143 int depth; 00144 /// Desc 00145 int verbose; 00146 /// Desc 00147 double *xmid; 00148 /// Desc 00149 double *sigma_l; 00150 /// Desc 00151 double *sigma_r; 00152 /// Desc 00153 double *fmax_l; 00154 /// Desc 00155 double *fmax_r; 00156 /// Desc 00157 double *fmin_l; 00158 /// Desc 00159 double *fmin_r; 00160 /// Desc 00161 double *fsum_l; 00162 /// Desc 00163 double *fsum_r; 00164 /// Desc 00165 double *fsum2_l; 00166 /// Desc 00167 double *fsum2_r; 00168 /// Desc 00169 size_t *hits_l; 00170 /// Desc 00171 size_t *hits_r; 00172 00173 /// Desc 00174 virtual int estimate_corrmc(func_t &func, size_t ndim, 00175 const vec_t &xl, const vec_t &xu, 00176 param_t &pa, size_t calls, double &res, 00177 double &err, const double lxmid[], 00178 double lsigma_l[], double lsigma_r[]) { 00179 size_t i, n; 00180 00181 double m = 0.0, q = 0.0; 00182 double vol = 1.0; 00183 00184 for (i = 0; i < dim; i++) { 00185 vol *= xu[i] - xl[i]; 00186 hits_l[i] = hits_r[i] = 0; 00187 fsum_l[i] = fsum_r[i] = 0.0; 00188 fsum2_l[i] = fsum2_r[i] = 0.0; 00189 lsigma_l[i] = lsigma_r[i] = -1; 00190 } 00191 00192 for (n = 0; n < calls; n++) { 00193 double fval; 00194 00195 unsigned int j = (n/2) % dim; 00196 unsigned int side = (n % 2); 00197 00198 for (i = 0; i < dim; i++) { 00199 00200 // The equivalent of gsl_rng_uniform_pos() 00201 double z; 00202 do { z=this->def_rng.random(); } while (z==0); 00203 00204 if (i != j) { 00205 x[i] = xl[i] + z * (xu[i] - xl[i]); 00206 } else { 00207 if (side == 0) { 00208 x[i] = lxmid[i] + z * (xu[i] - lxmid[i]); 00209 } else { 00210 x[i] = xl[i] + z * (lxmid[i] - xl[i]); 00211 } 00212 } 00213 } 00214 00215 //fval = GSL_MONTE_FN_EVAL (f, x); 00216 func(ndim,x,fval,pa); 00217 00218 /* recurrence for mean and variance */ 00219 { 00220 double d = fval - m; 00221 m += d / (n + 1.0); 00222 q += d * d * (n / (n + 1.0)); 00223 } 00224 00225 /* compute the variances on each side of the bisection */ 00226 for (i = 0; i < dim; i++) { 00227 if (x[i] <= lxmid[i]) { 00228 fsum_l[i] += fval; 00229 fsum2_l[i] += fval * fval; 00230 hits_l[i]++; 00231 } else { 00232 fsum_r[i] += fval; 00233 fsum2_r[i] += fval * fval; 00234 hits_r[i]++; 00235 } 00236 } 00237 } 00238 00239 for (i = 0; i < dim; i++) { 00240 double fraction_l = (lxmid[i] - xl[i]) / (xu[i] - xl[i]); 00241 00242 if (hits_l[i] > 0) { 00243 fsum_l[i] /= hits_l[i]; 00244 lsigma_l[i] = sqrt (fsum2_l[i] - fsum_l[i] * fsum_l[i] / hits_l[i]); 00245 lsigma_l[i] *= fraction_l * vol / hits_l[i]; 00246 } 00247 00248 if (hits_r[i] > 0) { 00249 fsum_r[i] /= hits_r[i]; 00250 lsigma_r[i] = sqrt (fsum2_r[i] - fsum_r[i] * fsum_r[i] / hits_r[i]); 00251 lsigma_r[i] *= (1 - fraction_l) * vol / hits_r[i]; 00252 } 00253 } 00254 00255 res = vol * m; 00256 00257 if (calls < 2) { 00258 err = GSL_POSINF; 00259 } else { 00260 err = vol * sqrt (q / (calls * (calls - 1.0))); 00261 } 00262 00263 return GSL_SUCCESS; 00264 } 00265 00266 /// Desc 00267 alloc_t ao; 00268 00269 /// Desc 00270 alloc_vec_t x; 00271 00272 #endif 00273 00274 public: 00275 00276 gsl_miser() { 00277 estimate_frac=0.1; 00278 alpha=2.0; 00279 dither=0.0; 00280 min_calls=100; 00281 min_calls_per_bisection=4000; 00282 } 00283 00284 /// Allocate memory 00285 virtual int allocate(size_t ldim) { 00286 00287 ao.allocate(x,ldim); 00288 00289 xmid = (double *) malloc (ldim * sizeof (double)); 00290 if (xmid == 0) { 00291 ao.free(x); 00292 GSL_ERROR_VAL ("failed to allocate space for xmid", GSL_ENOMEM, 0); 00293 } 00294 00295 sigma_l = (double *) malloc (ldim * sizeof (double)); 00296 if (sigma_l == 0) { 00297 std::free(xmid); 00298 ao.free(x); 00299 GSL_ERROR_VAL ("failed to allocate space for sigma_l", 00300 GSL_ENOMEM, 0); 00301 } 00302 00303 sigma_r = (double *) malloc (ldim * sizeof (double)); 00304 if (sigma_r == 0) { 00305 std::free(sigma_l); 00306 std::free(xmid); 00307 ao.free(x); 00308 GSL_ERROR_VAL ("failed to allocate space for sigma_r", 00309 GSL_ENOMEM, 0); 00310 } 00311 00312 fmax_l = (double *) malloc (ldim * sizeof (double)); 00313 if (fmax_l == 0) { 00314 std::free(sigma_r); 00315 std::free(sigma_l); 00316 std::free(xmid); 00317 ao.free(x); 00318 GSL_ERROR_VAL ("failed to allocate space for fmax_l", GSL_ENOMEM, 0); 00319 } 00320 00321 fmax_r = (double *) malloc (ldim * sizeof (double)); 00322 if (fmax_r == 0) { 00323 std::free(fmax_l); 00324 std::free(sigma_r); 00325 std::free(sigma_l); 00326 std::free(xmid); 00327 ao.free(x); 00328 GSL_ERROR_VAL ("failed to allocate space for fmax_r", GSL_ENOMEM, 0); 00329 } 00330 00331 fmin_l = (double *) malloc (ldim * sizeof (double)); 00332 if (fmin_l == 0) { 00333 std::free(fmax_r); 00334 std::free(fmax_l); 00335 std::free(sigma_r); 00336 std::free(sigma_l); 00337 std::free(xmid); 00338 ao.free(x); 00339 GSL_ERROR_VAL ("failed to allocate space for fmin_l", GSL_ENOMEM, 0); 00340 } 00341 00342 fmin_r = (double *) malloc (ldim * sizeof (double)); 00343 if (fmin_r == 0) { 00344 std::free(fmin_l); 00345 std::free(fmax_r); 00346 std::free(fmax_l); 00347 std::free(sigma_r); 00348 std::free(sigma_l); 00349 std::free(xmid); 00350 ao.free(x); 00351 GSL_ERROR_VAL ("failed to allocate space for fmin_r", GSL_ENOMEM, 0); 00352 } 00353 00354 fsum_l = (double *) malloc (ldim * sizeof (double)); 00355 if (fsum_l == 0) { 00356 std::free(fmin_r); 00357 std::free(fmin_l); 00358 std::free(fmax_r); 00359 std::free(fmax_l); 00360 std::free(sigma_r); 00361 std::free(sigma_l); 00362 std::free(xmid); 00363 ao.free(x); 00364 GSL_ERROR_VAL ("failed to allocate space for fsum_l", GSL_ENOMEM, 0); 00365 } 00366 00367 fsum_r = (double *) malloc (ldim * sizeof (double)); 00368 if (fsum_r == 0) { 00369 std::free(fsum_l); 00370 std::free(fmin_r); 00371 std::free(fmin_l); 00372 std::free(fmax_r); 00373 std::free(fmax_l); 00374 std::free(sigma_r); 00375 std::free(sigma_l); 00376 std::free(xmid); 00377 ao.free(x); 00378 GSL_ERROR_VAL ("failed to allocate space for fsum_r", GSL_ENOMEM, 0); 00379 } 00380 00381 fsum2_l = (double *) malloc (ldim * sizeof (double)); 00382 if (fsum2_l == 0) { 00383 std::free(fsum_r); 00384 std::free(fsum_l); 00385 std::free(fmin_r); 00386 std::free(fmin_l); 00387 std::free(fmax_r); 00388 std::free(fmax_l); 00389 std::free(sigma_r); 00390 std::free(sigma_l); 00391 std::free(xmid); 00392 ao.free(x); 00393 GSL_ERROR_VAL ("failed to allocate space for fsum2_l", 00394 GSL_ENOMEM, 0); 00395 } 00396 00397 fsum2_r = (double *) malloc (ldim * sizeof (double)); 00398 if (fsum2_r == 0) { 00399 std::free(fsum2_l); 00400 std::free(fsum_r); 00401 std::free(fsum_l); 00402 std::free(fmin_r); 00403 std::free(fmin_l); 00404 std::free(fmax_r); 00405 std::free(fmax_l); 00406 std::free(sigma_r); 00407 std::free(sigma_l); 00408 std::free(xmid); 00409 ao.free(x); 00410 GSL_ERROR_VAL ("failed to allocate space for fsum2_r", 00411 GSL_ENOMEM, 0); 00412 } 00413 00414 00415 hits_r = (size_t *) malloc (ldim * sizeof (size_t)); 00416 if (hits_r == 0) { 00417 std::free(fsum2_r); 00418 std::free(fsum2_l); 00419 std::free(fsum_r); 00420 std::free(fsum_l); 00421 std::free(fmin_r); 00422 std::free(fmin_l); 00423 std::free(fmax_r); 00424 std::free(fmax_l); 00425 std::free(sigma_r); 00426 std::free(sigma_l); 00427 std::free(xmid); 00428 ao.free(x); 00429 GSL_ERROR_VAL ("failed to allocate space for fsum2_r", 00430 GSL_ENOMEM, 0); 00431 } 00432 00433 hits_l = (size_t *) malloc (ldim * sizeof (size_t)); 00434 if (hits_l == 0) { 00435 std::free(hits_r); 00436 std::free(fsum2_r); 00437 std::free(fsum2_l); 00438 std::free(fsum_r); 00439 std::free(fsum_l); 00440 std::free(fmin_r); 00441 std::free(fmin_l); 00442 std::free(fmax_r); 00443 std::free(fmax_l); 00444 std::free(sigma_r); 00445 std::free(sigma_l); 00446 std::free(xmid); 00447 ao.free(x); 00448 GSL_ERROR_VAL ("failed to allocate space for fsum2_r", 00449 GSL_ENOMEM, 0); 00450 } 00451 00452 dim = ldim; 00453 00454 return 0; 00455 } 00456 00457 /// Free allocated memory 00458 virtual int free() { 00459 std::free(hits_r); 00460 std::free(hits_l); 00461 std::free(fsum2_r); 00462 std::free(fsum2_l); 00463 std::free(fsum_r); 00464 std::free(fsum_l); 00465 std::free(fmin_r); 00466 std::free(fmin_l); 00467 std::free(fmax_r); 00468 std::free(fmax_l); 00469 std::free(sigma_r); 00470 std::free(sigma_l); 00471 std::free(xmid); 00472 ao.free(x); 00473 return 0; 00474 } 00475 00476 /** 00477 \brief Integrate function \c func from x=a to x=b. 00478 */ 00479 virtual int miser_minteg_err(func_t &func, size_t ndim, const vec_t &xl, 00480 const vec_t &xu, size_t calls, param_t &pa, 00481 double &res, double &err) { 00482 00483 size_t n, estimate_calls, calls_l, calls_r; 00484 size_t i; 00485 size_t i_bisect; 00486 int found_best; 00487 00488 double res_est = 0, err_est = 0; 00489 double res_r = 0, err_r = 0, res_l = 0, err_l = 0; 00490 double xbi_l, xbi_m, xbi_r, s; 00491 00492 double vol; 00493 double weight_l, weight_r; 00494 00495 for (i = 0; i < dim; i++) { 00496 if (xu[i] <= xl[i]) { 00497 GSL_ERROR ("xu must be greater than xl", GSL_EINVAL); 00498 } 00499 00500 if (xu[i] - xl[i] > GSL_DBL_MAX) { 00501 GSL_ERROR ("Range of integration is too large, please rescale", 00502 GSL_EINVAL); 00503 } 00504 } 00505 00506 if (alpha < 0) { 00507 GSL_ERROR ("alpha must be non-negative", GSL_EINVAL); 00508 } 00509 00510 /* Compute volume */ 00511 00512 vol = 1; 00513 00514 for (i = 0; i < dim; i++) { 00515 vol *= xu[i] - xl[i]; 00516 } 00517 00518 if (calls < min_calls_per_bisection) { 00519 double m = 0.0, q = 0.0; 00520 00521 if (calls < 2) { 00522 GSL_ERROR ("insufficient calls for subvolume", GSL_EFAILED); 00523 } 00524 00525 for (n = 0; n < calls; n++) { 00526 /* Choose a random point in the integration region */ 00527 00528 for (i = 0; i < dim; i++) { 00529 00530 // The equivalent of gsl_rng_uniform_pos() 00531 double rdn; 00532 do { rdn=this->def_rng.random(); } while (rdn==0); 00533 00534 x[i] = xl[i] + rdn * (xu[i] - xl[i]); 00535 } 00536 00537 { 00538 double fval; 00539 func(ndim,x,fval,pa); 00540 00541 /* recurrence for mean and variance */ 00542 00543 double d = fval - m; 00544 m += d / (n + 1.0); 00545 q += d * d * (n / (n + 1.0)); 00546 } 00547 } 00548 00549 res = vol * m; 00550 00551 err = vol * sqrt (q / (calls * (calls - 1.0))); 00552 00553 return GSL_SUCCESS; 00554 } 00555 00556 // We explicitly make the typecast here 00557 estimate_calls=((size_t)GSL_MAX(min_calls,calls*(estimate_frac))); 00558 00559 if (estimate_calls < 4 * dim) { 00560 GSL_ERROR ("insufficient calls to sample all halfspaces", GSL_ESANITY); 00561 } 00562 00563 /* Flip coins to bisect the integration region with some fuzz */ 00564 00565 for (i = 0; i < dim; i++) { 00566 s=(this->def_rng.random()-0.5) >= 0.0 ? dither : -dither; 00567 xmid[i] = (0.5 + s) * xl[i] + (0.5 - s) * xu[i]; 00568 } 00569 00570 /* 00571 The idea is to chose the direction to bisect based on which will 00572 give the smallest total variance. We could (and may do so later) 00573 use MC to compute these variances. But the NR guys simply estimate 00574 the variances by finding the min and max function values 00575 for each half-region for each bisection. 00576 */ 00577 00578 estimate_corrmc(func,dim,xl,xu,pa,estimate_calls, 00579 res_est,err_est,xmid,sigma_l,sigma_r); 00580 00581 /* We have now used up some calls for the estimation */ 00582 00583 calls -= estimate_calls; 00584 00585 /* Now find direction with the smallest total "variance" */ 00586 00587 { 00588 double best_var = GSL_DBL_MAX; 00589 double beta = 2.0 / (1.0 + alpha); 00590 found_best = 0; 00591 i_bisect = 0; 00592 weight_l = weight_r = 1.0; 00593 00594 for (i = 0; i < dim; i++) { 00595 if (sigma_l[i] >= 0 && sigma_r[i] >= 0) { 00596 /* estimates are okay */ 00597 double var = pow (sigma_l[i], beta) + pow (sigma_r[i], beta); 00598 00599 if (var <= best_var) { 00600 found_best = 1; 00601 best_var = var; 00602 i_bisect = i; 00603 weight_l = pow (sigma_l[i], beta); 00604 weight_r = pow (sigma_r[i], beta); 00605 } 00606 } else { 00607 if (sigma_l[i] < 0) { 00608 GSL_ERROR ("no points in left-half space!", GSL_ESANITY); 00609 } 00610 if (sigma_r[i] < 0) { 00611 GSL_ERROR ("no points in right-half space!", GSL_ESANITY); 00612 } 00613 } 00614 } 00615 } 00616 00617 if (!found_best) { 00618 /* All estimates were the same, so chose a direction at random */ 00619 00620 i_bisect = this->def_rng.random_int(dim); 00621 } 00622 00623 xbi_l = xl[i_bisect]; 00624 xbi_m = xmid[i_bisect]; 00625 xbi_r = xu[i_bisect]; 00626 00627 /* Get the actual fractional sizes of the two "halves", and 00628 distribute the remaining calls among them */ 00629 00630 { 00631 double fraction_l = fabs ((xbi_m - xbi_l) / (xbi_r - xbi_l)); 00632 double fraction_r = 1 - fraction_l; 00633 00634 double a = fraction_l * weight_l; 00635 double b = fraction_r * weight_r; 00636 00637 calls_l = (size_t)(min_calls + (calls - 2 * min_calls) * a / (a + b)); 00638 calls_r = (size_t)(min_calls + (calls - 2 * min_calls) * b / (a + b)); 00639 } 00640 00641 /* Compute the integral for the left hand side of the bisection */ 00642 00643 /* Due to the recursive nature of the algorithm we must allocate 00644 some new memory for the integration limits for each recursive call */ 00645 00646 { 00647 int status; 00648 00649 alloc_vec_t xu_tmp; 00650 ao.allocate(xu_tmp,dim); 00651 00652 for (i = 0; i < dim; i++) { 00653 xu_tmp[i] = xu[i]; 00654 } 00655 00656 xu_tmp[i_bisect] = xbi_m; 00657 00658 status=miser_minteg_err(func,dim,xl,xu_tmp,calls_l,pa,res_l,err_l); 00659 00660 ao.free(xu_tmp); 00661 00662 if (status != GSL_SUCCESS) { 00663 return status; 00664 } 00665 } 00666 00667 /* Compute the integral for the right hand side of the bisection */ 00668 00669 { 00670 int status; 00671 00672 alloc_vec_t xl_tmp; 00673 ao.allocate(xl_tmp,dim); 00674 00675 for (i = 0; i < dim; i++) { 00676 xl_tmp[i] = xl[i]; 00677 } 00678 00679 xl_tmp[i_bisect] = xbi_m; 00680 00681 status=miser_minteg_err(func,dim,xl_tmp,xu,calls_r,pa,res_r,err_r); 00682 00683 ao.free(xl_tmp); 00684 00685 if (status != GSL_SUCCESS) { 00686 return status; 00687 } 00688 } 00689 00690 res=res_l+res_r; 00691 err=sqrt(err_l*err_l+err_r*err_r); 00692 00693 return 0; 00694 } 00695 00696 virtual ~gsl_miser() {} 00697 00698 /// Integrate function \c func from x=a to x=b. 00699 virtual int minteg_err(func_t &func, size_t ndim, const vec_t &a, 00700 const vec_t &b, param_t &pa, double &res, 00701 double &err) { 00702 allocate(ndim); 00703 min_calls=16*ndim; 00704 min_calls_per_bisection=32*min_calls; 00705 int ret=miser_minteg_err(func,ndim,a,b,this->n_points,pa,res,err); 00706 free(); 00707 return ret; 00708 } 00709 00710 /// Return string denoting type ("gsl_miser") 00711 virtual const char *type() { return "gsl_miser"; } 00712 00713 }; 00714 00715 #ifndef DOXYGENP 00716 } 00717 #endif 00718 00719 #endif 00720
Documentation generated with Doxygen and provided under the GNU Free Documentation License. See License Information for details.
Project hosting provided by
,
O2scl Sourceforge Project Page