package hep.aida.ref.pdf; import hep.aida.IAnnotation; import hep.aida.IModelFunction; import hep.aida.IRangeSet; import hep.aida.ref.Annotation; import java.util.ArrayList; /** * Base function. Any function should extend this class. * * @author The FreeHEP team @ SLAC. * */ public class Function extends Variable implements IModelFunction, VariableListener, FunctionDispatcher { private VariableList parList = new VariableList(VariableList.PARAMETER); private VariableList depList = new VariableList(VariableList.DEPENDENT); private VariableList funList = new VariableList(VariableList.FUNCTION); // private VariableList varList = new VariableList(VariableList.ANY); private ArrayList listeners = new ArrayList(); private boolean isNormalized = false; private double normalization = 1; private Parameter norm; private String codeletString; private Annotation annotation; public Function(String name) { super(name, Variable.FUNCTION); norm = new Parameter("norm",1); setCodeletString("codelet:"+this.getClass()+":class:"); annotation = new Annotation(); annotation.addItem(Annotation.titleKey, ""); annotation.setFillable(true); } /** * Variables Management. * */ public void addVariable(Variable v) { if ( v instanceof Dependent ) addDependent( (Dependent)v, true ); else if ( v instanceof Parameter ) addParameter( (Parameter)v, true ); else if ( v instanceof Function ) addFunction( (Function)v, true ); } public void addVariables(VariableList list) { for ( int i = 0; i < list.size(); i++ ) { Variable v = list.get(i); addVariable(v); variableChanged(v); } } private void addDependent(Dependent dep, boolean addListener) { checkVariable(dep); if ( addListener ) dep.addVariableListener(this); depList.add(dep); } private void addParameter(Parameter par, boolean addListener) { checkVariable(par); if ( addListener ) par.addVariableListener(this); parList.add(par); } private void addFunction(Function func, boolean addListener) { if ( addListener ) func.addVariableListener(this); funList.add(func); for ( int i = 0; i < func.numberOfDependents(); i++ ) addDependent( func.getDependent(i), false ); for ( int i = 0; i < func.numberOfParameters(); i++ ) addParameter( func.getParameter(i), false ); } private Function getCompositeFunction(Variable var) { for ( int i = 0; i < funList.size(); i++ ) { Function f = (Function) funList.get(i); if ( f.hasVariable(var) ) return f; } throw new IllegalArgumentException("Variable "+var.name()+" is not composite!"); } private void checkVariable(Variable var) { if ( ! isValidVariableName( var.name() ) ) throw new IllegalArgumentException("A Variable with name "+var.name()+" already belongs to this Function."); if ( hasVariable(var) ) throw new IllegalArgumentException("Variable "+var.name()+" already belongs to this Function"); } private boolean isValidVariableName(String name) { if ( depList.contains(name) || parList.contains(name) ) return false; if ( norm != null && name.equals( norm.name() ) ) return false; return true; } protected boolean hasDependent(Dependent dep) { return depList.contains(dep); } protected boolean hasParameter(Parameter par) { return parList.contains(par); } protected boolean hasVariable(Variable var) { if (depList.contains(var) || parList.contains(var) ) return true; if ( norm != null && var == norm ) return true; return false; } public boolean variableChangingUnits(Variable var, Units units) { return false; } public void variableChangedUnits(Variable var) { notifyFunctionChanged(new FunctionChangedEvent( FunctionChangedEvent.FUNCTION_CHANGED ) ); } public boolean variableChangingValue(Variable var, double value) { return true; } public String normalizationParameter() { return getNormalizationParameter().name(); } /** * This method is invoked when a variable in the function has changed its value. * */ public void variableChanged(Variable var) { } public void variableChangedValue(Variable var) { variableChanged(var); if ( parList.contains(var) ) { notifyFunctionChanged(new FunctionChangedEvent( FunctionChangedEvent.PARAMETER_VALUE_CHANGED ) ); updateNormalization(); } } public boolean variableChangingName(Variable var, String name) { return isValidVariableName(name); } public void variableChangedName(Variable var) { notifyFunctionChanged(new FunctionChangedEvent( FunctionChangedEvent.PARAMETER_NAME_CHANGED ) ); } public void setValue(double value) { super.setValue(value); notifyFunctionChanged(new FunctionChangedEvent( FunctionChangedEvent.FUNCTION_VALUE_CHANGED ) ); } public double functionValue() { throw new RuntimeException("This method MUST be overwritten!!"); } /** * This method is used to generate toy Mc data sets. * It should be overwritten by functions whose maximum value can be provided. * If it is not overwriten the maximum value will be evaluated with a mc * set of data points. * */ public double functionMaxValue() { return Double.NaN; } private double numericalIntegral() { return numericalIntegral(depList); } private double numericalIntegral(VariableList l) { int dim = l.size(); if ( dim == 0 ) return 0; double val = 0; //Save the dependent's value before integrating double[] x = new double[dim]; for ( int i = 0; i < x.length; i++ ) x[i] = l.get(i).value(); if ( dim == 1 ) val = FunctionIntegrator.integralTrapezoid(this, l); else val = FunctionIntegrator.integralMC(this, l); //Restore the dependent's value after the integration for ( int i = 0; i < x.length; i++ ) l.get(i).setValue(x[i]); return val; } protected void updateNormalization() { normalization = 0; VariableList l = new VariableList(VariableList.DEPENDENT); for ( int i = 0; i < numberOfDependents(); i++ ) { Dependent dep = getDependent(i); if ( ( ! isComposite(dep) ) && hasAnalyticalNormalization(dep) ) normalization += evaluateAnalyticalNormalization(dep); else l.add(dep); } if ( l.size() > 0 ) normalization += numericalIntegral(l); } /** * To be overwritten if Function provides analytical normalization. * */ public boolean hasAnalyticalNormalization(Dependent dep) { return false; } public double evaluateAnalyticalNormalization(Dependent dep) { VariableList l = new VariableList(VariableList.DEPENDENT); l.add(dep); return numericalIntegral(l); } protected double maxValue() { updateNormalization(); return functionMaxValue()/normalization; } public double value() { double fVal = functionValue(); if ( fVal < 0 ) { System.out.println("Negative value for "+name()+" : "+fVal+". Setting it to 0."); fVal = 0; } if (isNormalized) { fVal /= normalization; if ( isNormalized ) if ( fVal > 1 ) throw new RuntimeException("Probability greater than 1 for "+name()+" : "+fVal); } if ( norm != null ) fVal *= norm.value(); return fVal; } /** * To be overwritten by classes extending Function. * This method is used internally by this class to evaluate the derivative * with respect to a given Variable. * */ public double evaluateAnalyticalVariableGradient(Variable var) { throw new IllegalArgumentException("This function does not provide the gradient"); } private double evaluateVariableGradient(Variable var) { if ( var == norm ) return functionValue(); if ( providesGradientWithRespectToVariable(var) ) return getNormalizationParameter().value()*evaluateAnalyticalVariableGradient(var); else return FunctionDerivative.derivative(this, var, 1.); } /** * To be overwritten by classes extending Function. * This method is used internally by this class to determine if a function has * can provide an analytical gradient with respect to a given Variable. * */ public boolean hasAnalyticalVariableGradient(Variable var) { return false; } public boolean providesGradientWithRespectToVariable(Variable var) { if ( isComposite(var) ) { Function composite = getCompositeFunction(var); return composite.providesGradientWithRespectToVariable(var) && providesGradientWithRespectToVariable(composite); } else { if ( hasVariable(var) ) return hasAnalyticalVariableGradient(var); else throw new IllegalArgumentException("Variable "+var.name()+" does not belong to this function."); } } public double[] gradient() { double[] grad = new double[numberOfDependents()]; for ( int i = 0; i < grad.length; i++ ) grad[i] = evaluateVariableGradient(getDependent(i))/normalization; return grad; } public double[] parameterGradient() { double[] parGrad = new double[numberOfParameters()]; for ( int i = 0; i < parGrad.length; i++ ) parGrad[i] = evaluateVariableGradient(getParameter(i))/normalization; return parGrad; } public Parameter getParameter(int index) { if ( index == parList.size() && norm != null ) return norm; return (Parameter) parList.get(index); } public Parameter getParameter(String parName) { if ( norm != null && parName.equals(norm.name()) ) return norm; return (Parameter) parList.get(parName); } public int numberOfParameters() { int nPars = parList.size(); if ( ! isNormalized && norm != null ) nPars += 1; return nPars; } public Dependent getDependent(int index) { return (Dependent) depList.get(index); } public Dependent getDependent(String parName) { return (Dependent) depList.get(parName); } public int numberOfDependents() { return depList.size(); } public boolean isComposite(Variable var) { for ( int i = 0; i < funList.size(); i++ ) { Function f = (Function) funList.get(i); if ( f.hasVariable(var) ) return true; } return false; } /** * The normalization Parameter for a function is ALWAYS the last one. * */ public Parameter getNormalizationParameter() { return norm; } public void setNormalizationParamter(Parameter par) { norm = par; } public void addFunctionListener( FunctionListener listener ) { listeners.add(listener); } public void removeFunctionListener( FunctionListener listener ) { listeners.remove(listener); } protected void notifyFunctionChanged(FunctionChangedEvent event) { for ( int i = 0; i < listeners.size(); i++ ) ( (FunctionListener) listeners.get(i) ).functionChanged(event); } public void variableChangedRange(Variable var) { notifyFunctionChanged(new FunctionChangedEvent( FunctionChangedEvent.RANGE_CHANGED ) ); updateNormalization(); } public void normalizationRangeChanged() { notifyFunctionChanged(new FunctionChangedEvent( FunctionChangedEvent.RANGE_CHANGED ) ); updateNormalization(); } public boolean isNormalized() { return isNormalized; } public void normalize(boolean normalize) { Parameter norm = getNormalizationParameter(); if ( norm != null ) { if ( normalize ) { if ( ! isNormalized() ) { norm.setValue(1); norm.setFixed(true); } } else { norm.setFixed(false); } } isNormalized = normalize; updateNormalization(); } public boolean providesNormalization() { return false; } // Methods required by IModelFunction public IAnnotation annotation() { return annotation; } public String codeletString() { return codeletString; } public void setCodeletString(String str) { codeletString = str; } public double value(double[] v) { if ( v.length != dimension() ) throw new IllegalArgumentException("Illegal dimension for the vector "+v.length+". It should match the Function's dimension "+dimension()); for ( int i = 0; i < dimension(); i++ ) getDependent(i).setValue(v[i]); return value(); } public double[] gradient(double[] v) { if ( v.length != dimension() ) throw new IllegalArgumentException("Illegal dimension for the vector "+v.length+". It should match the Function's dimension "+dimension()); for ( int i = 0; i < dimension(); i++ ) getDependent(i).setValue(v[i]); return gradient(); } public int dimension() { return numberOfDependents(); } public int indexOfParameter(String parName) { return parList.indexOf(parName); } public double parameter(String parName) { return getParameter(parName).value(); } public String[] parameterNames() { String[] parNames = new String[numberOfParameters()]; for ( int i = 0; i < parNames.length; i++ ) parNames[i] = getParameter(i).name(); return parNames; } public double[] parameters() { double[] parValues = new double[numberOfParameters()]; for ( int i = 0; i < parValues.length; i++ ) parValues[i] = getParameter(i).value(); return parValues; } public void setParameter(String parName, double parValue) throws java.lang.IllegalArgumentException { getParameter(parName).setValue(parValue); } public void setParameters(double[] pars) throws java.lang.IllegalArgumentException { if ( pars.length != numberOfParameters() ) throw new IllegalArgumentException("Wrong number of input parameters:"+pars.length+", must be "+numberOfParameters()); for (int i=0; i<pars.length; i++) getParameter(i).setValue(pars[i]); } public void setTitle(String title) throws java.lang.IllegalArgumentException { annotation.setValue(Annotation.titleKey, title); } public String title() { return annotation.value(Annotation.titleKey); } public String variableName(int index) { return getDependent(index).name(); } public String[] variableNames() { String[] depNames = new String[dimension()]; for ( int i = 0; i < depNames.length; i++ ) depNames[i] = getDependent(i).name(); return depNames; } public double functionValue(double[] v) { if ( v.length != dimension() ) throw new IllegalArgumentException("Illegal dimension for the vector "+v.length+". It should match the Function's dimension "+dimension()); for ( int i = 0; i < dimension(); i++ ) getDependent(i).setValue(v[i]); return functionValue(); } public void excludeNormalizationAll() { for (int i=0; i<dimension(); i++) getDependent(i).range().excludeAll(); normalizationRangeChanged(); } public void includeNormalizationAll() { for (int i=0; i<dimension(); i++) getDependent(i).range().includeAll(); normalizationRangeChanged(); } public IRangeSet normalizationRange(int i) { return (IRangeSet) getDependent(i).range(); } public double[] parameterGradient(double[] v) { if ( v.length != dimension() ) throw new IllegalArgumentException("Illegal dimension for the vector "+v.length+". It should match the Function's dimension "+dimension()); for ( int i = 0; i < dimension(); i++ ) getDependent(i).setValue(v[i]); double[] tmpGrad = parameterGradient(); if ( isNormalized() ) return tmpGrad; double[] grad = new double[tmpGrad.length+1]; for ( int i = 0; i < tmpGrad.length; i++ ) grad[i] = tmpGrad[i]; grad[grad.length-1] = functionValue(); return grad; } public boolean isEqual(hep.aida.IFunction iFunction) { throw new UnsupportedOperationException("This method has not been implemented."); } public boolean providesGradient() { for ( int i = 0; i < numberOfDependents(); i++ ) { if ( ! providesGradientWithRespectToVariable( getDependent(i) ) ) return false; } return true; } public boolean providesParameterGradient() { for ( int i = 0; i < numberOfParameters(); i++ ) { if ( ! providesGradientWithRespectToVariable( getParameter(i) ) ) return false; } return true; } protected void setVariableValue(double value) { throw new IllegalArgumentException("Cannot set the value of a function "); } }