View Javadoc
1   package ffx.xray.refine;
2   
3   import ffx.potential.bonded.Atom;
4   
5   import java.util.ArrayList;
6   import java.util.List;
7   
8   import static java.lang.Math.asin;
9   import static java.lang.Math.sin;
10  import static java.lang.Math.sqrt;
11  import static java.lang.String.format;
12  
13  /**
14   * The RefinableOccupancy class represents an atom and manages its occupancy value
15   * along with any constrained atoms that share the same occupancy constraints.
16   * It allows setting and retrieving the occupancy value, ensuring that all
17   * constrained atoms are synchronized with the primary atom's occupancy.
18   */
19  public class RefinedOccupancy extends RefinedParameter {
20  
21    private static final double OCCUPANCY_SCALE = 120.0;
22  
23    /**
24     * A collection of atoms that are constrained in relation to a primary {@link Atom}.
25     * These constrained atoms are defined as part of the experimental refinement process
26     * and are related to the primary atom based on specific refinement requirements.
27     * <p>
28     * The refined parameter(s) will be applied to the constrained Atoms, but because these
29     * atoms do not contribute to experimental scattering, their partial derivatives do not
30     * need to be considered.
31     * <p>
32     * The occupancy of these atoms will be set to 1.0 - occupancy of this parameter.
33     */
34    protected final List<Atom> complementList;
35  
36    /**
37     * A collection of {@link Atom} instances that are both constrained with respect to a primary {@link Atom}
38     * and directly contribute to experimental scattering.
39     * <p>
40     * The partial derivative of the experimental refinement target with respect to this
41     * parameter(s) is contributed to by each constrained atom that scatters.
42     * <p>
43     * The occupancy of these atoms will be set to 1.0 - occupancy of this parameter.
44     * There contribution to the gradient will be negated.
45     */
46    protected final List<Atom> complementScatterList;
47  
48    /**
49     * Constructs a RefinableOccupancy instance for managing the specified atom.
50     *
51     * @param atom the primary atom whose occupancy value will be managed and potentially constrained
52     *             to synchronize with other atoms
53     */
54    public RefinedOccupancy(Atom atom) {
55      super(atom);
56      complementList = new ArrayList<>();
57      complementScatterList = new ArrayList<>();
58    }
59  
60    @Override
61    public void addConstrainedAtom(Atom atom) {
62      // Apply the constraint.
63      atom.setActive(true);
64      atom.setOccupancy(getOccupancy());
65      constrainedAtoms.add(atom);
66    }
67  
68    @Override
69    public void addConstrainedAtomThatScatters(Atom atom) {
70      // Apply the constraint.
71      atom.setActive(true);
72      atom.setOccupancy(getOccupancy());
73      constrainedAtomsThatScatter.add(atom);
74    }
75  
76    /**
77     * Adds an atom constrained to this RefinedOccupancy instance. The specified
78     * atom will have its active status enabled and its occupancy value set to complement the
79     * occupancy of the primary atom managed by this instance.
80     *
81     * @param atom the atom to be added as a complementary, constrained atom. Its occupancy will
82     *             be adjusted to ensure it complements the occupancy of the primary atom.
83     */
84    public void addConstrainedAtomComplement(Atom atom) {
85      // Apply the constraint.
86      atom.setActive(true);
87      atom.setOccupancy(1.0 - getOccupancy());
88      complementList.add(atom);
89    }
90  
91    /**
92     * Adds an atom constrained to this RefinedOccupancy instance as a complementary, scattering atom.
93     * The specified atom will have its active status enabled and its occupancy value set to complement
94     * the occupancy of the primary atom managed by this instance. Additionally, the atom will be added
95     * to the complementScatterList.
96     *
97     * @param atom the atom to be added as a complementary, constrained, scattering atom. Its occupancy
98     *             will be adjusted to ensure it complements the occupancy of the primary atom.
99     */
100   public void addConstrainedAtomThatScattersComplement(Atom atom) {
101     // Apply the constraint.
102     atom.setActive(true);
103     atom.setOccupancy(1.0 - getOccupancy());
104     complementScatterList.add(atom);
105   }
106 
107   @Override
108   public int getNumberOfParameters() {
109     return 1;
110   }
111 
112   /**
113    * Retrieves the occupancy value of the primary atom managed by this instance.
114    * The occupancy represents the fractional occupancy of the atom in the molecular structure.
115    *
116    * @return the occupancy value of the primary atom as a double
117    */
118   public double getOccupancy() {
119     return atom.getOccupancy();
120   }
121 
122   /**
123    * Sets the occupancy value for the primary atom and any constrained atoms, ensuring
124    * that all atoms share the same occupancy value. The occupancy represents the
125    * fractional occupancy of the atom in the molecular structure.
126    *
127    * @param occupancy the new occupancy value to be assigned to the primary atom and
128    *                  all constrained atoms
129    */
130   public void setOccupancy(double occupancy) {
131     atom.setOccupancy(occupancy);
132     for (Atom a : constrainedAtoms) {
133       a.setOccupancy(occupancy);
134     }
135     for (Atom a : constrainedAtomsThatScatter) {
136       a.setOccupancy(occupancy);
137     }
138     for (Atom a : complementList) {
139       a.setOccupancy(1.0 - occupancy);
140     }
141     for (Atom a : complementScatterList) {
142       a.setOccupancy(1.0 - occupancy);
143     }
144   }
145 
146   /**
147    * Updates the specified parameters array with the occupancy value of the primary atom
148    * managed by this instance at the specified index.
149    *
150    * @param parameters an array of doubles representing the parameters to be updated.
151    */
152   @Override
153   public void getParameters(double[] parameters) {
154     double occupancy = atom.getOccupancy();
155     // Convert occupancy to theta:
156     // occupancy = sin^2(theta)
157     // theta = asin(sqrt(occupancy);
158     parameters[index] = asin(sqrt(occupancy));
159   }
160 
161   /**
162    * Sets the occupancy value for the primary atom and any
163    * constrained atoms using a specified index within the parameters array.
164    *
165    * @param parameters an array of doubles containing all refined parameters.
166    */
167   @Override
168   public void setParameters(double[] parameters) {
169     // occupancy = sin^2(theta)
170     double sinTheta = sin(parameters[index]);
171     double occupancy = sinTheta * sinTheta;
172     setOccupancy(occupancy);
173   }
174 
175   /**
176    * Updates the specified parameters array with the occupancy velocity of the primary atom
177    * managed by this instance at the specified index. The velocity represents the rate of change
178    * of the atom's occupancy with units of degrees per picosecond.
179    *
180    * @param velocity an array of doubles to be updated with the atom's occupancy velocity.
181    */
182   @Override
183   public void getVelocity(double[] velocity) {
184     // Occupancy is stored from 0 to 1: occupancy = sin^2(theta).
185     // Theta is sent to the integrator / optimizer: theta = asin(sqrt(occupancy)).
186     // The occupancy "particle" has velocity with units of degrees / picosecond.
187     double v = atom.getOccupancyVelocity();
188     velocity[index] = v;
189   }
190 
191   /**
192    * Updates the occupancy velocity for the primary atom managed by this instance
193    * using a specified value from the parameters array at the specified index.
194    *
195    * @param velocity an array of doubles representing refined parameters,
196    *                 where the relevant velocity value is extracted and applied.
197    */
198   @Override
199   public void setVelocity(double[] velocity) {
200     atom.setOccupancyVelocity(velocity[index]);
201   }
202 
203   /**
204    * Updates the specified acceleration array with the occupancy acceleration
205    * of the primary atom managed by this instance at the relevant index.
206    * The acceleration represents the second derivative of the atom's occupancy
207    * with respect to time.
208    *
209    * @param acceleration an array of doubles to be updated with the atom's occupancy acceleration.
210    */
211   @Override
212   public void getAcceleration(double[] acceleration) {
213     double v = atom.getOccupancyAcceleration();
214     acceleration[index] = v;
215   }
216 
217   /**
218    * Sets the occupancy acceleration for the primary atom based on the specified array.
219    * This method updates the occupancy acceleration of the atom managed by this instance
220    * using the value at the specified index.
221    *
222    * @param acceleration an array of doubles representing the acceleration values
223    *                     to be used for updating the atom's occupancy acceleration.
224    */
225   @Override
226   public void setAcceleration(double[] acceleration) {
227     atom.setOccupancyAcceleration(acceleration[index]);
228   }
229 
230   /**
231    * Updates the specified previous acceleration array with the occupancy acceleration
232    * of the primary atom managed by this instance at the relevant index.
233    * The previous acceleration represents the second derivative of the atom's occupancy
234    * with respect to time.
235    *
236    * @param previousAcceleration an array of doubles to be updated with the atom's occupancy acceleration.
237    */
238   @Override
239   public void getPreviousAcceleration(double[] previousAcceleration) {
240     double v = atom.getOccupancyAcceleration();
241     previousAcceleration[index] = v;
242   }
243 
244   /**
245    * Sets the occupancy previous acceleration for the primary atom based on the specified array.
246    * This method updates the occupancy previous acceleration of the atom managed by this instance
247    * using the value at the specified index.
248    *
249    * @param previousAcceleration an array of doubles representing the previous acceleration values
250    *                             to be used for updating the atom's occupancy acceleration.
251    */
252   @Override
253   public void setPreviousAcceleration(double[] previousAcceleration) {
254     atom.setOccupancyAcceleration(previousAcceleration[index]);
255   }
256 
257   @Override
258   public void setOptimizationScaling(double[] optimizationScaling) {
259     optimizationScaling[index] = OCCUPANCY_SCALE;
260   }
261 
262   /**
263    * Resets the occupancy gradient associated with the primary atom and any constrained
264    * atoms to zero. This method ensures that the occupancy gradient for the primary
265    * atom, constrained atoms, and constrained atoms that scatter are all reset to a value of 0.0.
266    * <p>
267    * The occupancy gradient represents the partial derivative of the energy with
268    * respect to the atom's occupancy, and this reset is part of the gradient update process.
269    */
270   @Override
271   public void zeroGradient() {
272     atom.setOccupancyGradient(0.0);
273     for (Atom a : constrainedAtoms) {
274       a.setOccupancyGradient(0.0);
275     }
276     for (Atom a : constrainedAtomsThatScatter) {
277       a.setOccupancyGradient(0.0);
278     }
279     for (Atom a : complementList) {
280       a.setOccupancyGradient(0.0);
281     }
282     for (Atom a : complementScatterList) {
283       a.setOccupancyGradient(0.0);
284     }
285   }
286 
287   /**
288    * Updates the specified gradient array with the occupancy gradients of the primary atom and any
289    * constrained atoms that scatter.
290    *
291    * @param gradient an array of doubles representing the gradient to be updated.
292    */
293   @Override
294   public void getGradient(double[] gradient) {
295     gradient[index] = atom.getOccupancyGradient();
296     for (Atom a : constrainedAtomsThatScatter) {
297       gradient[index] += a.getOccupancyGradient();
298     }
299     for (Atom a : complementScatterList) {
300       gradient[index] -= a.getOccupancyGradient();
301     }
302     // Add the chain rule term:
303     // dE / dTheta = dE / dOcc * dOcc / dTheta
304     // dE / dTheta = dE / dOcc * sin(2 * theta)
305     double theta = asin(sqrt(getOccupancy()));
306     double sin2Theta = sin(2.0 * theta);
307     gradient[index] *= sin2Theta;
308   }
309 
310   /**
311    * Updates the specified mass array with the default mass value at the relevant index.
312    *
313    * @param mass        an array of doubles representing the mass values to be updated.
314    * @param defaultMass a double representing the default mass value to assign.
315    */
316   @Override
317   public void getMass(double[] mass, double defaultMass) {
318     mass[index] = defaultMass;
319   }
320 
321   @Override
322   public String toString() {
323     return format(" Occupancy: %10.6f", atom.getOccupancy());
324   }
325 }