#delimit ;
program define polyspline, rclass;
version 16.0;
/*
 Create a basis for a polynomial or other unrestrictd spline
 in an X-variable,
 with corresponding parameters equal to values of the spline
 at user-specified reference points,
 or differences between those values.
 This program is an easy-to-use front end
 for the bspline package.
*! Author: Roger Newson
*! Date: 21 May 2024
*/

syntax varname [if] [in] , Generate(passthru)
  [
  Refpts(numlist sort min=2)
  Power(numlist integer min=1 max=1)
  OMit(passthru) BAse(passthru)
  INClude(passthru)
  Type(passthru)
  LABfmt(passthru) LABPrefix(passthru)
  ];
/*
 refpts() is the list of reference points.
 power() is the power (or degree) of the spline.
 Other options are passed to flexcurv.
*/

*
 Check package dependencies
*;
qui _whichlist bspline frencurv flexcurv, package(bspline);
if trim("`r(incomplete)'")=="bspline" {;
  disp as error "Package bspline must be installed for polyspline to work."
    _n as error "To install bspline from SSC, type:"
	_n as input "ssc install bspline";
  error 498;
};

*
 Count reference points
 and set power if necessary
*;
local refpts: list uniq refpts;
local nrefpt: word count `refpts';
if `nrefpt'==0 {;
  local nrefpt=2;
};
else if `nrefpt'==1 {;
  disp _n as error "refpts() invalid";
  error 122;
};
if "`power'"=="" {;
  local power=`nrefpt'-1;
};
if `power'>`nrefpt'-1 {;
  local power=`nrefpt'-1;
};
if `power'<1 {;
  disp _n as error "power() must be an integer >= 1";
  error 498;
};

*
 Generate splines
*;
qui flexcurv `if' `in', xvar(`varlist') refpts(`refpts') power(`power')
  krule(interpolate)
  `include' `omit' `base' `generate' `type' `labfmt' `labprefix';
local nspline=r(nspline);
return add;
disp as text "`nspline' reference splines generated of degree: " as result `power';

end;


prog def _whichlist, rclass;
version 16.0;
/*
 Input a list of which input items
 and optionally a package list
 and output lists of present and absent items
 and complete and incomplete packages.
*/


syntax anything(name=itemlist) [ , Packagelist(namelist) NOIsily ];
*
 packagelist() specifies a list of packages
   for the items to belong to.
 noisily specifies that whichlist will have the output generated by which
   for each item in the item list.
*;
local Nitem: word count `itemlist';

*
 Extend packagelist if required
*;
if "`packagelist'"!="" {;
  local Npackage: word count `packagelist';
  if `Npackage' < `Nitem' {;
    local lastpackage: word `Npackage' of `packagelist';
    forv i1=`=`Npackage'+1'(1)`Nitem' {;
      local packagelist "`packagelist' `lastpackage'";
    };
  };
};


*
 Create present, absent, complete, and incomplete lists
*;
if "`packagelist'"=="" {;
  * Create present and absent lists only *;
  forv i1=1(1)`Nitem' {;
    local itemcur: word `i1' of `itemlist';
    cap `noisily' which `itemcur';
    if _rc local absent `"`absent' `itemcur'"';
    else local present `"`present' `itemcur'"';
  };
};
else {;
  * Create present, absent, complete, and incomplete lists *;
  forv i1=1(1)`Nitem' {;
    local itemcur: word `i1' of `itemlist';
    local packagecur: word `i1' of `packagelist';
    cap `noisily' which `itemcur';
    if _rc {;
      local absent `"`absent' `itemcur'"';
      local incomplete "`incomplete' `packagecur'";
    };
    else {;
      local present `"`present' `itemcur'"';
      local complete "`complete' `packagecur'";
    };
  };
  local incomplete: list uniq incomplete;
  local incomplete: list sort incomplete;
  local complete: list uniq complete;
  local complete: list complete - incomplete;
  local complete: list sort complete;
};
local present: list uniq present;
local present: list sort present;
local absent: list uniq absent;
local absent: list sort absent;


*
 List results
*;
if `"`present'"'!="" {;
  disp as text "Present items:";
  disp as result `"`present'"';
};
if `"`absent'"'!="" {;
  disp as text "Absent items:";
  disp as result `"`absent'"';
};
if "`complete'"!="" {;
  disp as text "Complete packages:";
  disp as result `"`complete'"';
};
if "`incomplete'"!="" {;
  disp as text "Incomplete packages:";
  disp as result `"`incomplete'"';
};


*
 Return results
*;
foreach R in incomplete complete absent present {;
  return local `R' `"``R''"';
};


end;