IMP logo
IMP Reference Guide  develop.63b38c487d,2024/12/21
The Integrative Modeling Platform
WeightedProfileFitter.h
Go to the documentation of this file.
1 /**
2  * \file IMP/saxs/WeightedProfileFitter.h
3  * \brief Fitting of multiple profiles to the experimental one.
4  * The weights of the profiles are computed analytically using
5  * non-negative least squares fitting (NNLS)
6  *
7  * \authors Dina Schneidman
8  * Copyright 2007-2022 IMP Inventors. All rights reserved.
9  *
10  */
11 
12 #ifndef IMPSAXS_WEIGHTED_PROFILE_FITTER_H
13 #define IMPSAXS_WEIGHTED_PROFILE_FITTER_H
14 
15 #include "ProfileFitter.h"
16 #include "ChiScore.h"
17 #include "WeightedFitParameters.h"
18 #include "nnls.h"
19 #include <Eigen/Dense>
20 
21 IMPSAXS_BEGIN_NAMESPACE
22 
23 //! Fitting of multiple profiles to the experimental one.
24 /** The weights of the profiles are computed analytically using
25  non-negative least squares fitting (NNLS).
26 */
27 template <typename ScoringFunctionT = ChiScore>
28 class WeightedProfileFitter : public ProfileFitter<ScoringFunctionT> {
29 
30  public:
31  //! Constructor
32  /**
33  \param[in] exp_profile Experimental profile we want to fit
34  */
35  WeightedProfileFitter(const Profile* exp_profile) :
36  ProfileFitter<ScoringFunctionT>(exp_profile),
37  W_(exp_profile->size(), 1),
38  Wb_(exp_profile->size()),
39  A_(exp_profile->size(), 2) {
40 
41  Eigen::VectorXf b(exp_profile->size());
42  for (unsigned int i = 0; i < exp_profile->size(); i++) {
43  Wb_(i) = exp_profile->get_intensity(i);
44  W_(i) = 1.0 / (exp_profile->get_error(i));
45  }
46  Wb_ = W_.asDiagonal() * Wb_;
47  }
48 
49 // When used from Python, compute_score will return the weights as a second
50 // return value, rather than modifying a passed-in vector
51 #ifdef SWIG
52 %typemap(in, numinputs=0) Vector<double>& weights (Vector<double> temp) {
53  $1 = &temp;
54 }
55 %typemap(argout) Vector<double>& weights {
56  PyObject *obj = ConvertSequence<Vector<double>, Convert<double>>::create_python_object(ValueOrObject<Vector<double>>::get(*$1), $descriptor(double*), SWIG_POINTER_OWN);
57  $result = SWIG_AppendOutput($result, obj);
58 }
59 #endif
60 
61  //! compute a weighted score that minimizes chi
62  /**
63  it is assumed that the q values of the profiles are the same as
64  the q values of the experimental profile. Use Profile::resample to resample
65  if(NNLS = true, solve non-negative least squares, otherwise solve just
66  least squares, that may return negative weights to be discarded later
67  */
68  double compute_score(const ProfilesTemp& profiles, Vector<double>& weights,
69  bool use_offset = false, bool NNLS = true) const;
70 
71 #ifdef SWIG
72 %clear Vector<double>& weights;
73 #endif
74 
75  //! fit profiles by optimization of c1/c2 and weights
76  /**
77  it is assumed that the q values of the profiles are the same as
78  the q values of the experimental profile. Use Profile::resample to resample
79  */
80  WeightedFitParameters fit_profile(ProfilesTemp partial_profiles,
81  double min_c1 = 0.95, double max_c1 = 1.05,
82  double min_c2 = -2.0, double max_c2 = 4.0,
83  bool use_offset = false) const;
84 
85  //! write a fit file
86  void write_fit_file(ProfilesTemp partial_profiles,
87  const WeightedFitParameters& fp,
88  const std::string fit_file_name,
89  bool use_offset = false) const;
90 
91  private:
92  WeightedFitParameters search_fit_parameters(
93  ProfilesTemp& partial_profiles, double min_c1, double max_c1, double min_c2,
94  double max_c2, double old_chi, Vector<double>& weights, bool use_offset) const;
95 
96  private:
97  Eigen::MatrixXf W_; // weights matrix
98 
99  // weights matrix multiplied by experimental intensities vector
100  Eigen::VectorXf Wb_;
101 
102  // intensities
103  Eigen::MatrixXf A_;
104 };
105 
106 template <typename ScoringFunctionT>
108  const ProfilesTemp& profiles,
109  Vector<double>& weights,
110  bool use_offset, bool nnls) const {
111 
112  // no need to compute weighted profile for ensemble of size 1
113  if (profiles.size() == 1) {
114  weights.resize(1);
115  weights[0] = 1.0;
118  profiles[0], use_offset);
119  }
120 
121  // compute_weights(profiles, weights);
122  int m = profiles.size();
124 
125  // fill in A_
126  WeightedProfileFitter* non_const_this =
127  const_cast<WeightedProfileFitter*>(this);
128 
129  if (A_.cols() != m) non_const_this->A_.resize(n, m);
130  for (int j = 0; j < m; j++) {
131  for (int i = 0; i < n; i++) {
132  (non_const_this->A_)(i, j) = profiles[j]->get_intensity(i);
133  }
134  }
135 
136  Eigen::VectorXf w;
137  if (!nnls) { // solve least squares
138  Eigen::JacobiSVD<Eigen::MatrixXf> svd(W_.asDiagonal() * A_,
139  Eigen::ComputeThinU
140  | Eigen::ComputeThinV);
141  w = svd.solve(Wb_);
142  // zero the negatives
143  for (int i = 0; i < w.size(); i++)
144  if (w(i) < 0) w(i) = 0;
145  } else {
146  w = NNLS(W_.asDiagonal() * A_, Wb_);
147  }
148  w /= w.sum();
149 
150  // compute weighted profile
151  IMP_NEW(Profile, weighted_profile,
155  Eigen::VectorXf wp = A_ * w;
156  weighted_profile->set_qs(profiles[0]->get_qs());
157  weighted_profile->set_intensities(wp);
158  weights.resize(w.size());
159  for (int i = 0; i < w.size(); i++) weights[i] = w[i];
160 
163  weighted_profile, use_offset);
164 }
165 
166 template <typename ScoringFunctionT>
168  ProfilesTemp partial_profiles,
169  double min_c1, double max_c1,
170  double min_c2, double max_c2,
171  bool use_offset) const {
172  Vector<double> weights;
174  search_fit_parameters(partial_profiles, min_c1, max_c1, min_c2, max_c2,
175  std::numeric_limits<double>::max(), weights, use_offset);
176  return fp;
177 }
178 
179 template <typename ScoringFunctionT>
181  ProfilesTemp partial_profiles,
182  const WeightedFitParameters& fp,
183  const std::string fit_file_name,
184  bool use_offset) const {
185  double best_c1 = fp.get_c1();
186  double best_c2 = fp.get_c2();
187 
188  // compute a profile for best c1/c2 combination
189  for (unsigned int i = 0; i < partial_profiles.size(); i++)
190  partial_profiles[i]->sum_partial_profiles(best_c1, best_c2);
191 
192  if (partial_profiles.size() == 1) {
193  // compute scale
194  double offset = 0.0;
195  if (use_offset)
196  offset =
199  partial_profiles[0]);
200 
201  double c =
204  partial_profiles[0], offset);
206  fit_file_name, partial_profiles[0], fp.get_chi_square(), c, offset);
207  } else {
208 
209  // computed weighted profile
210  IMP_NEW(Profile, weighted_profile,
214 
215  const Vector<double>& weights = fp.get_weights();
216  for (unsigned int i = 0; i < weights.size(); i++)
217  weighted_profile->add(partial_profiles[i], weights[i]);
218 
219  // compute scale
220  double offset = 0.0;
221  if (use_offset)
222  offset =
225  weighted_profile);
226  double c =
229  weighted_profile, offset);
230 
232  fit_file_name, weighted_profile,
233  fp.get_chi_square(), c, offset);
234  }
235 }
236 
237 template <typename ScoringFunctionT>
239  ProfilesTemp& partial_profiles,
240  double min_c1, double max_c1,
241  double min_c2, double max_c2,
242  double old_chi, Vector<double>& weights,
243  bool use_offset) const {
244  int c1_cells = 10;
245  int c2_cells = 10;
246  if (old_chi < (std::numeric_limits<double>::max() - 1)) { // second iteration
247  c1_cells = 5;
248  c2_cells = 5;
249  }
250 
251  double delta_c1 = (max_c1 - min_c1) / c1_cells;
252  double delta_c2 = (max_c2 - min_c2) / c2_cells;
253 
254  bool last_c1 = false;
255  bool last_c2 = false;
256  if (delta_c1 < 0.0001) {
257  c1_cells = 1;
258  delta_c1 = max_c1 - min_c1;
259  last_c1 = true;
260  }
261  if (delta_c2 < 0.001) {
262  c2_cells = 1;
263  delta_c2 = max_c2 - min_c2;
264  last_c2 = true;
265  }
266  double best_c1(1.0), best_c2(0.0), best_chi(old_chi);
267  bool best_set = false;
268 
269  // c1 iteration
270  double c1(min_c1);
271  for (int i = 0; i <= c1_cells; i++, c1 += delta_c1) {
272  // c2 iteration
273  double c2 = min_c2;
274  for (int j = 0; j <= c2_cells; j++, c2 += delta_c2) {
275  // sum up the profiles for c1/c2 combo
276  for (unsigned int k = 0; k < partial_profiles.size(); k++)
277  partial_profiles[k]->sum_partial_profiles(c1, c2);
278  Vector<double> curr_weights;
279  double curr_chi = compute_score(partial_profiles, curr_weights, use_offset);
280  if (!best_set || curr_chi < best_chi) {
281  best_set = true;
282  best_chi = curr_chi;
283  best_c1 = c1;
284  best_c2 = c2;
285  weights = curr_weights;
286  }
287  }
288  }
289 
290  if (std::fabs(best_chi - old_chi) > 0.005 &&
291  (!(last_c1 && last_c2))) { // refine more
292  min_c1 = std::max(best_c1 - delta_c1, min_c1);
293  max_c1 = std::min(best_c1 + delta_c1, max_c1);
294  min_c2 = std::max(best_c2 - delta_c2, min_c2);
295  max_c2 = std::min(best_c2 + delta_c2, max_c2);
296  return search_fit_parameters(partial_profiles, min_c1, max_c1, min_c2,
297  max_c2, best_chi, weights, use_offset);
298  }
299  return WeightedFitParameters(best_chi, best_c1, best_c2, weights);
300 }
301 
302 IMPSAXS_END_NAMESPACE
303 
304 #endif /* IMPSAXS_WEIGHTED_PROFILE_FITTER_H */
unsigned int size() const
return number of entries in SAXS profile
Definition: Profile.h:181
Basic chi score implementation.
WeightedProfileFitter(const Profile *exp_profile)
Constructor.
Eigen::VectorXf NNLS(const Eigen::MatrixXf &A, const Eigen::VectorXf &b)
non-negative least square fitting for profile weight solving problem
Definition: nnls.h:19
a class for fitting two profiles
A more IMP-like version of the std::vector.
Definition: Vector.h:50
#define IMP_NEW(Typename, varname, args)
Declare a ref counted pointer to a new object.
Definition: object_macros.h:74
Fitting of multiple profiles to the experimental one.
IMP::Vector< IMP::WeakPointer< Profile > > ProfilesTemp
Definition: Profile.h:336
double compute_offset(const Profile *model_profile) const
computes offset for fitting for fitting the modeled profile
Definition: ProfileFitter.h:88
Parameters of a weighted fit, from WeightedProfileFitter.
Fit two profiles with user-defined scoring function as a template parameter.
Definition: ProfileFitter.h:29
double compute_score(const Profile *model_profile, bool use_offset=false, const std::string fit_file_name="") const
compute fit score
double compute_scale_factor(const Profile *model_profile, double offset=0.0) const
computes the scaling factor needed for fitting the modeled profile
Definition: ProfileFitter.h:76