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 }