OSCR

Axon Diameter Mapping in the Living Human Brain with Ultra-High-Gradient Diffusion MRI at 500 mT/m Gradient Strength.

Code ↔ Paper

5 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 5 matches · 1 of them tie a paragraph to a whole file, not to given lines: a weak match, whose lines are not tinted
  1. [1] § Methods › AxCaliber‐SMT Model Fitting › Forward Model and Assumptions ↔ AxCaliberSMT/gpuAxCaliberSMT.m, lines 81–127 · score 0.83 · AxCaliber, SMT model, axial diffusivity, CSF diffusivity, intrinsic diffusivity, extra cellular
  2. [2] § Methods › AxCaliber‐SMT Model Fitting › Markov Chain Monte Carlo With Ensemble Sampler With Affine Invariance ↔ utils/mcmc.m, lines 385–442 · score 0.64 · affine invariant ensemble, sampler, Goodman, Weare, MCMC, dimensional
  3. [3] § Methods › AxCaliber‐SMT Model Fitting › Markov Chain Monte Carlo With Ensemble Sampler With Affine Invariance ↔ utils/mcmc.m, lines 385–442 · score 0.57 · stretch move, walkers, thinned, Sampler, Affine, Ensemble
  4. [4] § Methods › MRI Acquisition ↔ helpers/diffusion_parameters_exporter.py, the whole file · a weak match · score 0.53 · encoding directions, pulse width, 30 ms, volumes, diffusion
  5. [5] § Methods › Noise Propagation From Synthetic Diffusion Signal ↔ MCRMWI/gpuMCRMWI.m, lines 566–714 · score 0.51 · intra axonal, volume fraction, forward model, component, extracellular, water

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

MATLAB · 878 lines · 48 KB · GPL-3.0 · 2 matches

  1. classdef mcmc < handle
  2. % Kwok-Shing Chan @ MGH
  3. % [email hidden]
  4. %
  5. % This is the class of all MCMC related functions
  6. %
  7. % Date created: 13 June 2024
  8. % Date modified: 7 August 2024
  9. % Date modified: 23 August 2024
  10. % Date modified: 5 October 2024
  11. % Date modified: 4 June 2026 (update affine-invariant method with and without global parameters)
  12. %
  13. properties (GetAccess = public, SetAccess = protected)
  14. end
  15. methods
  16. function out = optimisation(this, data, mask, weights, pars0, fitting, FWDfunc, varargin)
  17. % Input
  18. % ----------
  19. % data : N-D measurement data, First 3 dims reserve for spatial info
  20. % mask : M-D signal mask (M=[1,3])
  21. % weights : N-D weights for optimisaiton, same dim as 'data'
  22. % pars0 : Structure variable containing all parameters to be estimated
  23. % fitting : Structure variable containing all fitting algorithm setting
  24. % .modelParams : 1xM cell variable, name of the model parameters, e.g. {'S0','R2star','noise'};
  25. % .lb : 1xM numeric variable, fitting lower bound, same order as field 'modelParams', e.g. [0.5, 0, 0.001];
  26. % .ub : 1xM numeric variable, fitting upper bound, same order as field 'modelParams', e.g. [2, 1, 0.1];
  27. % .algorithm : MCMC algorithm, 'MH'|'GW'
  28. % .iteration : # MCMC iterations
  29. % .thinning : sampling interval between iterations
  30. % .burnin : iterations at the beginning to be discarded, if burnin>1, then the exact number will be used; if 0<burnin<1 then actual burnin = iteration*burnin
  31. % .repetition : # repetition of MCMC proposal
  32. % .xStepSize : step size of model parameter in MCMC proposal, same size and order as 'modelParams' ('MH' only)
  33. % .StepSize : step size for 'GW' in MCMC proposal ('GW' only)
  34. % .Nwalker : # random walkers ('GW' only)
  35. % FWDfunc : function handle of forward model
  36. % varargin : contains additional input requires for FWDfunc
  37. %
  38. fitting = this.check_set_default_basic(fitting);
  39. % Step 0: display basic messages
  40. this.display_basic_algorithm_parameters(fitting);
  41. % mask data to reduce memory load
  42. mask_idx = find(mask>0);
  43. if ~ismatrix(data); data = utils.reshape_ND2GD(data, mask_idx); else; data = data(:,mask_idx); end
  44. if ~ismatrix(weights); weights = utils.reshape_ND2GD(weights, mask_idx); elseif ~isempty(weights); weights = weights(:,mask_idx); end
  45. % data = utils.reshape_ND2GD(data,mask);
  46. % if ~isempty(weights); weights = utils.reshape_ND2GD(weights,mask); else; weights = ones(size(data), 'like', data); end
  47. pars0 = utils.reshape_ND2GD_struct(pars0,mask);
  48. isGlobal = false;
  49. for k = 1:numel(fitting.modelParams)
  50. if k == 1
  51. N = numel(pars0.(fitting.modelParams{k}));
  52. else
  53. if N ~= numel(pars0.(fitting.modelParams{k})) && numel(pars0.(fitting.modelParams{k})) == 1
  54. isGlobal = true;
  55. break;
  56. end
  57. end
  58. end
  59. % MCMC
  60. if strcmpi(fitting.algorithm,'mh')
  61. xPosterior = this.metropolis_hastings(data, pars0, weights, fitting, FWDfunc ,varargin{:});
  62. else
  63. if ~isGlobal
  64. xPosterior = this.goodman_weare(data, pars0, weights, fitting, FWDfunc ,varargin{:});
  65. else
  66. xPosterior = this.goodman_weare_wglobal_constant(data, pars0, weights, fitting, FWDfunc ,varargin{:});
  67. end
  68. end
  69. % finish up
  70. out = this.res2out(xPosterior,fitting,mask);
  71. end
  72. function xPosterior = metropolis_hastings(this,y,x0,weights,fitting,FWDfunc,varargin)
  73. % Input
  74. % ------
  75. % y : measurements, [Nmeas,Nvoxels]
  76. % x0 : structure array, starting points, N fields, each field 1xNvoxel
  77. % weights : weighting for non-linear least square fitting, same dimension as y
  78. % fitting : Structure variable containing all fitting algorithm setting
  79. % .modelParams : 1xM cell variable, name of the model parameters, e.g. {'S0','R2star','noise'};
  80. % .lb : 1xM numeric variable, fitting lower bound, same order as field 'modelParams', e.g. [0.5, 0, 0.001];
  81. % .ub : 1xM numeric variable, fitting upper bound, same order as field 'modelParams', e.g. [2, 1, 0.1];
  82. % .iteration : # MCMC iterations
  83. % .thinning : sampling interval between iterations
  84. % .burnin : iterations at the beginning to be discarded, if burnin>1, then the exact number will be used; if 0<burnin<1 then actual burnin = iteration*burnin
  85. % .repetition : # repetition of MCMC proposal
  86. % .xStepSize : step size of model parameter in MCMC proposal, same size and order as 'modelParams' ('MH' only)
  87. % FWDfunc : function handle for forward signal model
  88. % varargin : other input required for @FWDfunc
  89. %
  90. fitting = this.check_set_default_basic(fitting);
  91. if isempty(weights); weights = ones(size(y), 'like', y); end
  92. % Nm: # measurements; Nv: # voxels
  93. [Nm, Nv] = size(y);
  94. % Nvar: # estimation parameters
  95. Nvar = numel(fitting.modelParams);
  96. Nburnin = this.get_number_burnin(fitting);
  97. % Ns: # samples in posterior distribution
  98. Ns = numel(Nburnin+1:fitting.thinning:fitting.iteration); %floor( (fitting.iteration - floor(fitting.iteration*fitting.burnin)) / fitting.thinning );
  99. % convert data into single datatype for better performance and out themn into GPU
  100. y = gpuArray( single(y) );
  101. weights = gpuArray( single(weights) );
  102. for km = 1:Nvar; x0.(fitting.modelParams{km}) = gpuArray(single( x0.(fitting.modelParams{km}) ));end
  103. xStepsize = gpuArray(single(fitting.xStepSize(:)));
  104. % setup boundary variables
  105. lb = gpuArray( single(repmat(fitting.lb(:),1,Nv)));
  106. ub = gpuArray( single(repmat(fitting.ub(:),1,Nv)));
  107. % initialize array to staore all the samples
  108. xPosterior = zeros(Nvar, Nv, Ns, fitting.repetition,'single');
  109. % compute likelihood at starting points
  110. % logP is converted into external function for specific CUDA kernel
  111. % logP = @(X, Y) -sum( (this.FWD(X(1:4, :), model)-Y).^2, 1 )./(2*X(5,:).^2) + Nm/2*log(1./X(5,:).^2);
  112. xCurr = this.struct2array(x0,fitting.modelParams); % extract parameter structure to numeric array for faster computation
  113. xCurr = max(xCurr,lb); xCurr = min(xCurr,ub); % set boundary
  114. x0 = this.array2struct(xCurr,fitting.modelParams); % convert array back to structure for FWD function
  115. logP0 = arrayfun(@logP_Gaussian, sum( weights.* (FWDfunc(x0,varargin{:})-y).^2, 1 ), x0.noise, Nm);
  116. disp('-------------------------');
  117. disp('MCMC optimisation process');
  118. disp('-------------------------');
  119. % loop (multiple) proposal (same start)
  120. for ii = 1:fitting.repetition
  121. fprintf('Repetition #%i/%i \n',ii,fitting.repetition)
  122. % reset start point
  123. logPCurr = logP0;
  124. xCurr = this.struct2array(x0,fitting.modelParams);
  125. counter = 0; start = tic;
  126. for k = 1:fitting.iteration
  127. % 1. make a proposal with normal distribution
  128. % proposal is generated during iteration
  129. xProposed = xCurr + xStepsize.*randn(size(xCurr),'like',xCurr);
  130. % find proposal that is out of bound for exclusion
  131. isOutofbound = max(or(xProposed<lb, xProposed>ub),[],1);
  132. % replace out of bound by boundary values to avoid error when compting probability
  133. xProposed = max(xProposed,lb); xProposed = min(xProposed,ub);
  134. % convert the proposal into structure array for FWD function
  135. xProposed_struct = this.array2struct(xProposed,fitting.modelParams);
  136. % 2. Metropolis sampling
  137. % If the probability ratio of new to old > threshold, we take the new solution.
  138. % 2.1 proposal probability
  139. logPProposed = arrayfun(@logP_Gaussian, sum( weights.* (FWDfunc(xProposed_struct, varargin{:})-y).^2, 1 ), xProposed_struct.noise, Nm);
  140. % 2.2 Compute acceptance ratio based on new/old probability
  141. acceptanceRatio = min(exp(logPProposed-logPCurr), 1);
  142. isAccepted = acceptanceRatio > rand(1,Nv,'like',logPProposed);
  143. isAccepted(isOutofbound)= 0; % reject out of bound proposal
  144. % 2.3 update parameters if accepted
  145. logPCurr(isAccepted) = logPProposed(isAccepted);
  146. xCurr(:,isAccepted) = xProposed(:,isAccepted);
  147. % 3. Maintain the independence between iterations
  148. % 3.1 discard the first burnin*100% iterations
  149. % 3.2 keep an iteration every N iterations
  150. if ( k > Nburnin ) && mod(k-Nburnin+1, fitting.thinning) == 0 %( mod(k, fitting.thinning)==1 )
  151. counter = counter+1;
  152. xPosterior(:,:,counter,ii) = gather(xCurr);
  153. end
  154. % display message at 1000 iteration and every 10000 iteration
  155. if mod(k,fitting.iteration/50) == 0 || k == min(1e3, fitting.iteration/100)
  156. ET = duration(0,0,toc(start),'Format','hh:mm:ss');
  157. ERT = ET / (k/fitting.iteration) - ET;
  158. fprintf('Iteration #%6d, Elapsed time (hh:mm:ss):%s, Estimated remaining time (hh:mm:ss):%s \n',k,string(ET),string(ERT));
  159. end
  160. end
  161. end
  162. % convert final posterior distribution into structure
  163. xPosterior = this.array2struct(xPosterior,fitting.modelParams);
  164. for kvar = 1:Nvar; xPosterior.(fitting.modelParams{kvar}) = shiftdim(xPosterior.(fitting.modelParams{kvar}),1); end
  165. disp('The Metroplis-Hastings MCMC sampling is completed.')
  166. end
  167. function xPosterior = goodman_weare(this,y,x0,weights,fitting,modelFWD,varargin)
  168. % Input
  169. % ----------
  170. % y : measurements, [Nmeas,Nvoxels]
  171. % x0 : structure array, starting points, N fields, each field 1xNvoxel
  172. % weights : weighting for non-linear least square fitting, same dimension as y
  173. % pars0 : Structure variable containing all parameters to be estimated
  174. % fitting : Structure variable containing all fitting algorithm setting
  175. % .modelParams : 1xM cell variable, name of the model parameters, e.g. {'S0','R2star','noise'};
  176. % .lb : 1xM numeric variable, fitting lower bound, same order as field 'modelParams', e.g. [0.5, 0, 0.001];
  177. % .ub : 1xM numeric variable, fitting upper bound, same order as field 'modelParams', e.g. [2, 1, 0.1];
  178. % .iteration : # MCMC iterations
  179. % .thinning : sampling interval between iterations
  180. % .burnin : iterations at the beginning to be discarded, if burnin>1, then the exact number will be used; if 0<burnin<1 then actual burnin = iteration*burnin
  181. % .repetition : # repetition of MCMC proposal
  182. % .StepSize : step size for 'GW' in MCMC proposal ('GW' only)
  183. % .Nwalker : # random walkers ('GW' only)
  184. % .Ensembleupdate : (optional) ensemble update scheme:
  185. % 'simultaneous' - original behaviour (DEFAULT, backward compatible):
  186. % all walkers proposed/updated in one pass using a
  187. % single derangement as partners.
  188. % 'redblack' - affine-invariance-correct parallel update
  189. % (Foreman-Mackey et al. 2013): split the ensemble
  190. % into two halves and update each half using the
  191. % other (frozen) half as anchors.
  192. % FWDfunc : function handle of forward model
  193. % varargin : contains additional input requires for FWDfunc
  194. %
  195. % ENSEMBLE SAMPLERS WITH AFFINE INVARIANCE (2010) JONATHAN GOODMAN AND JONATHAN WEARE
  196. % Other references:
  197. % emcee: The MCMC Hammer (2013) https://arxiv.org/pdf/1202.3665
  198. % https://github.com/grinsted/gwmcmc/tree/master
  199. %
  200. fitting = this.check_set_default_basic(fitting);
  201. if isempty(weights); weights = ones(size(y), 'like', y); end
  202. % Nm: # measurements; Nv: # voxels
  203. [Nm, Nv] = size(y);
  204. % Nvar: # estimation parameters
  205. Nvar = numel(fitting.modelParams);
  206. Nburnin = this.get_number_burnin(fitting);
  207. % Ns: # samples in posterior distribution
  208. Ns = numel(Nburnin+1:fitting.thinning:fitting.iteration);
  209. Nwalker = fitting.Nwalker;
  210. StepSize = fitting.StepSize;
  211. % red-black update scheme
  212. useRedBlack = strcmpi(fitting.Ensembleupdate,'redblack');
  213. if useRedBlack
  214. if Nwalker < 2
  215. error('goodman_weare:Nwalker', ...
  216. 'redblack update needs Nwalker >= 2 (use even, >= 2*Nvar in practice).');
  217. end
  218. if mod(Nwalker,2) ~= 0
  219. warning('goodman_weare:Nwalker', ...
  220. 'Nwalker is odd; redblack halves will be unequal. Even Nwalker is conventional.');
  221. end
  222. h = floor(Nwalker/2);
  223. halfIdx = {1:h, h+1:Nwalker}; % {S0, S1}: complementary anchor sets
  224. end
  225. fprintf('Ensemble update scheme: %s\n', fitting.Ensembleupdate);
  226. % convert data into single datatype for better performance
  227. y = gpuArray( single(y) );
  228. weights = gpuArray( single(weights) );
  229. for km = 1:Nvar; x0.(fitting.modelParams{km}) = gpuArray(single( x0.(fitting.modelParams{km}) ));end
  230. % setup boundary variables
  231. lb = gpuArray( single(repmat(fitting.lb(:),1,Nv,Nwalker)));
  232. ub = gpuArray( single(repmat(fitting.ub(:),1,Nv,Nwalker)));
  233. % set up weight
  234. weights = repmat(weights,1,1,Nwalker);
  235. % initialize array to staore all the samples
  236. xPosterior = zeros(Nvar, Nv, Nwalker, Ns, fitting.repetition,'single');
  237. % initiate an ensemble of walkers around the starting position (0.1% full range) with Gaussian distribution
  238. % 1st: Nvar;2nd: Nv; 3rd: Nwalker
  239. xCurr = this.struct2array(x0,fitting.modelParams); % extract parameter structure to numeric array for faster computation
  240. xCurr = xCurr + (ub-lb)*fitting.startRange.*randn(size(ub)); % initiate starting position for all walkers
  241. xCurr = max(xCurr,lb); xCurr = min(xCurr,ub); % set boundary
  242. x0 = this.array2struct(xCurr,fitting.modelParams); % convert array back to structure for FWD function
  243. % compute likelihood at starting points
  244. logP0 = arrayfun(@logP_Gaussian, sum( weights.* (modelFWD(x0, varargin{:})-y).^2, 1 ), x0.noise, Nm);
  245. disp('-------------------------');
  246. disp('MCMC optimisation process');
  247. disp('-------------------------');
  248. for ii = 1:fitting.repetition
  249. fprintf('Repetition #%i/%i \n',ii,fitting.repetition)
  250. logPCurr= logP0;
  251. xCurr = this.struct2array(x0,fitting.modelParams);
  252. counter = 0; start = tic;
  253. for k = 1:fitting.iteration
  254. if ~useRedBlack
  255. % =========== LEGACY: simultaneous single-derangement update ===========
  256. % (identical to the original implementation; preserved for reproducibility)
  257. % 1.1 find a unique partner for each walker
  258. % 1. make a proposal with normal distribution
  259. % 1.1. find a unique partner for a walker k
  260. partner = this.find_partner(Nwalker);
  261. % 1.2. stretch move
  262. zz = ((StepSize-1)*rand(size(logP0),'like',xCurr) + 1).^2 / StepSize;
  263. xProposed = xCurr(:,:,partner) + (xCurr - xCurr(:,:,partner)).*zz;
  264. % find proposal that is out of bound for exclusion
  265. isOOB = max(or(xProposed<lb, xProposed>ub),[],1);
  266. % replace boundary values so it does not give error when compting probability
  267. xProposed = max(xProposed,lb); xProposed = min(xProposed,ub);
  268. % convert the proposal into structure array for FWD function
  269. xProposed_struct = this.array2struct(xProposed,fitting.modelParams);
  270. % 2. Metropolis sampling
  271. % If the probability ratio of new to old > threshold, we take the new solution.
  272. % 2.1 proposal probability
  273. logPProposed = arrayfun(@logP_Gaussian, sum( weights.* (modelFWD(xProposed_struct, varargin{:})-y).^2, 1 ), xProposed_struct.noise, Nm);
  274. % 2.2 Compute acceptance ratio based on z^(Nd-1)*new/old probability
  275. acceptanceRatio = min(zz.^(Nvar-1).*exp(logPProposed-logPCurr), 1);
  276. isAccepted = acceptanceRatio > rand(1,Nv,Nwalker,'like',xCurr);
  277. isAccepted(isOOB) = 0; % reject out of bound proposal
  278. % 2.3 update parameters
  279. logPCurr(isAccepted) = logPProposed(isAccepted);
  280. xCurr(:,isAccepted) = xProposed(:,isAccepted);
  281. else
  282. % =========== RED-BLACK: two complementary sub-steps per sweep ===========
  283. for s = 1:2
  284. active = halfIdx{s}; % walkers moved this sub-step
  285. frozen = halfIdx{3-s}; % anchors, held FIXED this sub-step
  286. na = numel(active);
  287. % stretch proposal: each active walker anchored on a RANDOM frozen
  288. % walker (uniform, WITH replacement -- textbook GW partner draw)
  289. pidx = frozen(randi(numel(frozen),1,na));
  290. xAnch = xCurr(:,:,pidx); % [Nvar,Nv,na] fixed
  291. xAct = xCurr(:,:,active); % [Nvar,Nv,na]
  292. zz = ((StepSize-1)*rand(1,Nv,na,'like',xCurr) + 1).^2 / StepSize;
  293. xProp = xAnch + (xAct - xAnch).*zz;
  294. lb_a = lb(:,:,active); ub_a = ub(:,:,active);
  295. isOOB = max(or(xProp<lb_a, xProp>ub_a),[],1);
  296. xProp = max(xProp,lb_a); xProp = min(xProp,ub_a);
  297. % proposal likelihood on the active half only
  298. % (two half-width evals/iter ~= one full eval: no cost regression)
  299. xProp_s = this.array2struct(xProp,fitting.modelParams);
  300. logPProp = arrayfun(@logP_Gaussian, ...
  301. sum( weights(:,:,active).*(modelFWD(xProp_s,varargin{:})-y).^2, 1 ), ...
  302. xProp_s.noise, Nm);
  303. % acceptance z^(Nvar-1) * pi(new)/pi(old)
  304. logPAct = logPCurr(:,:,active);
  305. aR = min(zz.^(Nvar-1).*exp(logPProp - logPAct), 1);
  306. acc = aR > rand(1,Nv,na,'like',xCurr);
  307. acc(isOOB) = 0;
  308. % scatter accepted moves back into the active block
  309. xAct(:,acc) = xProp(:,acc);
  310. logPAct(acc) = logPProp(acc);
  311. xCurr(:,:,active) = xAct;
  312. logPCurr(:,:,active) = logPAct;
  313. end
  314. end
  315. % 3. Maintain the independence between iterations
  316. % 3.1 discard the first burnin*100% iterations
  317. % 3.2 keep an iteration every N iterations
  318. if ( k > Nburnin ) && mod(k-Nburnin+1, fitting.thinning) == 0
  319. counter = counter+1;
  320. xPosterior(:,:,:,counter,ii) = gather(xCurr);
  321. end
  322. % display message at 1000 ietration and every 2000 iterations
  323. if mod(k,fitting.iteration/50) == 0 || k == min(1e2, fitting.iteration/100)
  324. ET = duration(0,0,toc(start),'Format','hh:mm:ss');
  325. ERT = ET / (k/fitting.iteration) - ET;
  326. fprintf('Iteration #%6d, Elapsed time (hh:mm:ss):%s, Estimated remaining time (hh:mm:ss):%s \n',k,string(ET),string(ERT));
  327. end
  328. end
  329. end
  330. % convert final posterior distribution into structure
  331. xPosterior = this.array2struct(xPosterior,fitting.modelParams);
  332. for kvar = 1:Nvar; xPosterior.(fitting.modelParams{kvar}) = shiftdim(xPosterior.(fitting.modelParams{kvar}),1); end
  333. disp('The affine-invariant ensemble MCMC sampling is completed.')
  334. end
  335. function xPosterior = goodman_weare_wglobal_constant(this,y,x0,weights,fitting,modelFWD,varargin)
  336. % Affine-invariant ensemble sampling for hierarchical / partial-pooling
  337. % problems with GLOBAL (shared) parameters, via block Metropolis-within-Gibbs.
  338. %
  339. % CONTRACT (scope of this sampler):
  340. % The log-likelihood must factorise over units (voxels) as
  341. % sum_z loglik(y_z | theta_z, phi)
  342. % i.e. given the global parameters phi, the units are conditionally
  343. % independent. Parameters split into LOCAL theta_z (one set per unit) and
  344. % GLOBAL phi (shared). Any model with this structure is supported
  345. % (arbitrary NND local and NGlobal global parameters); the sampler holds
  346. % no model-specific knowledge.
  347. %
  348. % WHICH PARAMETERS ARE GLOBAL is detected from the starting structure: a
  349. % field of x0 with size==1 along the voxel dimension is treated as global
  350. % (see check_global_constant). So {R2c,k2A}, a shared diffusivity, a global
  351. % B1 term, etc., all work without code changes.
  352. %
  353. % One iteration = two decoupled blocks, each a Goodman-Weare stretch move:
  354. % LOCAL block : propose theta, GLOBALS HELD FIXED, exponent z^(NND-1),
  355. % forward model evaluated at (new local, old global).
  356. % GLOBAL block: propose phi, LOCALS HELD FIXED at their updated values,
  357. % exponent z^(NGlobal-1), forward model evaluated at
  358. % (current local, new global); acceptance uses the summed
  359. % log-likelihood DIFFERENCE accumulated in double precision.
  360. %
  361. % This structure is what keeps the cached likelihood consistent with the
  362. % current state at all times (the previous single-eval / two-accept scheme
  363. % left it stale) and gives each block its correct stretch Jacobian.
  364. %
  365. % NOTES / options:
  366. % .StepSize : stretch scale 'a'
  367. % .Nwalker : # walkers (even, >= 2*max(NND,NGlobal) recommended)
  368. % .startRange : local walker init spread (fraction of range)
  369. % .globalWarmup : (optional) # global-only iterations before the joint
  370. % loop (locals frozen). Default 0. Helps when the global
  371. % posterior is very tight.
  372. % .Ensembleupdate : ensemble update scheme, applied to BOTH blocks:
  373. % 'simultaneous' - (DEFAULT, backward compatible)
  374. % all walkers proposed at once using a
  375. % single derangement as partners.
  376. % 'redblack' - correct parallel update
  377. % (Foreman-Mackey 2013): split walkers
  378. % into two halves and update each half
  379. % using the other as frozen anchors.
  380. % Consistent with the option in goodman_weare.
  381. %
  382. % Goodman & Weare 2010; emcee (Foreman-Mackey 2013).
  383. fitting = this.check_set_default_basic(fitting);
  384. if isempty(weights); weights = ones(size(y), 'like', y); end
  385. [Nm, Nv] = size(y);
  386. Nvar = numel(fitting.modelParams);
  387. Nburnin = this.get_number_burnin(fitting);
  388. Ns = numel(Nburnin+1:fitting.thinning:fitting.iteration);
  389. Nwalker = fitting.Nwalker;
  390. StepSize = fitting.StepSize;
  391. % --- ensemble update scheme (consistent with goodman_weare) -----
  392. useRedBlack = strcmpi(fitting.Ensembleupdate,'redblack');
  393. if useRedBlack
  394. if mod(Nwalker,2) ~= 0
  395. warning('goodman_weare_wglobal_constant:Nwalker', ...
  396. 'Nwalker is odd; red-black halves will be unequal. Even Nwalker is conventional.');
  397. end
  398. h = floor(Nwalker/2);
  399. halfIdx = {1:h, h+1:Nwalker};
  400. end
  401. fprintf('Ensemble update scheme: %s\n', fitting.Ensembleupdate);
  402. % -----------------------------------------------------------------
  403. % convert to single + push to GPU
  404. y = gpuArray( single(y) );
  405. weights = gpuArray( single(weights) );
  406. for km = 1:Nvar; x0.(fitting.modelParams{km}) = gpuArray(single( x0.(fitting.modelParams{km}) )); end
  407. isGlobal = this.check_global_constant(x0,fitting.modelParams);
  408. NGlobal = numel(isGlobal(isGlobal==1));
  409. NND = Nvar - NGlobal;
  410. if NND==0 || NGlobal==0
  411. error('goodman_weare_wglobal_constant:partition', ...
  412. 'Need at least one local and one global parameter (NND=%d, NGlobal=%d). Use goodman_weare for the all-local case.',NND,NGlobal);
  413. end
  414. % bounds
  415. lb = gpuArray( single(repmat(fitting.lb(isGlobal==0),1,Nv,Nwalker)));
  416. ub = gpuArray( single(repmat(fitting.ub(isGlobal==0),1,Nv,Nwalker)));
  417. lb_global = gpuArray( single(repmat(fitting.lb(isGlobal==1),1,1,Nwalker)));
  418. ub_global = gpuArray( single(repmat(fitting.ub(isGlobal==1),1,1,Nwalker)));
  419. weights = repmat(weights,1,1,Nwalker);
  420. xPosterior_ND = zeros(NND, Nv, Nwalker, Ns, fitting.repetition,'single');
  421. xPosterior_global = zeros(NGlobal, 1, Nwalker, Ns, fitting.repetition,'single');
  422. % --- initialise walkers -------------------------------------------------
  423. [xCurr_ND,xCurr_global] = this.struct2array_wglobal(x0,fitting.modelParams);
  424. % local: tight cloud around starting point
  425. xCurr_ND = xCurr_ND + (ub-lb)*fitting.startRange.*randn(size(ub));
  426. xCurr_ND = min(max(xCurr_ND,lb),ub);
  427. % GLOBAL: OVER-DISPERSED across the full prior box. Ensemble samplers
  428. % cannot manufacture spread a collapsed cloud lacks, and the global
  429. % posterior is typically very tight (informed by all voxels), so a small
  430. % cloud can get stuck. Over-dispersion contracts correctly; collapse does not.
  431. % xCurr_global = lb_global + (ub_global-lb_global).*rand(size(ub_global));
  432. xCurr_global = xCurr_global + (ub_global-lb_global)*fitting.startRange.*rand(size(ub_global));
  433. x0 = this.array2struct_wglobal(xCurr_ND,xCurr_global,fitting.modelParams,isGlobal);
  434. logP0 = arrayfun(@logP_Gaussian, sum( weights.* (modelFWD(x0,varargin{:})-y).^2, 1 ), x0.noise, Nm);
  435. disp('-------------------------');
  436. disp('MCMC optimisation process (block GW: local + global)');
  437. disp('-------------------------');
  438. for ii = 1:fitting.repetition
  439. fprintf('Repetition #%i/%i \n',ii,fitting.repetition)
  440. logPCurr = logP0;
  441. [xCurr_ND,xCurr_global] = this.struct2array_wglobal(x0,fitting.modelParams);
  442. % optional global-only warmup (locals frozen) to settle the tight global block
  443. for kw = 1:fitting.globalWarmup
  444. [xCurr_global,logPCurr] = this.gw_global_block( ...
  445. xCurr_ND,xCurr_global,logPCurr,weights,y,Nm,Nv,Nwalker, ...
  446. StepSize,NGlobal,lb_global,ub_global,fitting,isGlobal,modelFWD,varargin{:});
  447. end
  448. counter = 0; start = tic;
  449. for k = 1:fitting.iteration
  450. % ============ LOCAL block (globals fixed) ============
  451. if ~useRedBlack
  452. % --- simultaneous (default, original behaviour) ---
  453. partner = this.find_partner(Nwalker);
  454. zz_ND = ((StepSize-1)*rand(1,Nv,Nwalker,'like',xCurr_ND) + 1).^2 / StepSize;
  455. xProp_ND = xCurr_ND(:,:,partner) + (xCurr_ND - xCurr_ND(:,:,partner)).*zz_ND;
  456. oob_ND = max(or(xProp_ND<lb, xProp_ND>ub),[],1);
  457. xProp_ND = min(max(xProp_ND,lb),ub);
  458. s_local = this.array2struct_wglobal(xProp_ND, xCurr_global, fitting.modelParams, isGlobal);
  459. logP_loc = arrayfun(@logP_Gaussian, sum( weights.*(modelFWD(s_local,varargin{:})-y).^2, 1 ), s_local.noise, Nm);
  460. aR = min( zz_ND.^(NND-1).*exp(logP_loc - logPCurr), 1);
  461. acc = aR > rand(1,Nv,Nwalker,'like',xCurr_ND);
  462. acc(oob_ND) = 0;
  463. logPCurr(acc) = logP_loc(acc); % cache <- (new local, OLD global)
  464. xCurr_ND(:,acc) = xProp_ND(:,acc);
  465. else
  466. % --- red-black: two sub-steps over walker halves ---
  467. for s = 1:2
  468. active = halfIdx{s}; frozen = halfIdx{3-s}; na = numel(active);
  469. pidx = frozen(randi(numel(frozen),1,na));
  470. xAnch = xCurr_ND(:,:,pidx); xAct = xCurr_ND(:,:,active);
  471. zz_ND = ((StepSize-1)*rand(1,Nv,na,'like',xCurr_ND) + 1).^2 / StepSize;
  472. xProp = xAnch + (xAct - xAnch).*zz_ND;
  473. lb_a = lb(:,:,active); ub_a = ub(:,:,active);
  474. isOOB = max(or(xProp<lb_a, xProp>ub_a),[],1);
  475. xProp = min(max(xProp,lb_a),ub_a);
  476. % eval at (new local active, OLD global active) -- globals fixed this block
  477. s_loc = this.array2struct_wglobal(xProp, xCurr_global(:,:,active), fitting.modelParams, isGlobal);
  478. logP_loc = arrayfun(@logP_Gaussian, sum( weights(:,:,active).*(modelFWD(s_loc,varargin{:})-y).^2, 1 ), s_loc.noise, Nm);
  479. logPAct = logPCurr(:,:,active);
  480. aR = min( zz_ND.^(NND-1).*exp(logP_loc - logPAct), 1);
  481. acc = aR > rand(1,Nv,na,'like',xCurr_ND); acc(isOOB) = 0;
  482. xAct(:,acc) = xProp(:,acc);
  483. logPAct(acc) = logP_loc(acc);
  484. xCurr_ND(:,:,active) = xAct;
  485. logPCurr(:,:,active) = logPAct;
  486. end
  487. end
  488. % ============ GLOBAL block (locals fixed at updated values) ============
  489. [xCurr_global,logPCurr] = this.gw_global_block( ...
  490. xCurr_ND,xCurr_global,logPCurr,weights,y,Nm,Nv,Nwalker, ...
  491. StepSize,NGlobal,lb_global,ub_global,fitting,isGlobal,modelFWD,varargin{:});
  492. % thinning / burn-in
  493. if ( k > Nburnin ) && mod(k-Nburnin+1, fitting.thinning) == 0
  494. counter = counter+1;
  495. xPosterior_ND(:,:,:,counter,ii) = gather(xCurr_ND);
  496. xPosterior_global(:,:,:,counter,ii) = gather(xCurr_global);
  497. end
  498. if mod(k,fitting.iteration/50) == 0 || k == min(1e2, fitting.iteration/100)
  499. ET = duration(0,0,toc(start),'Format','hh:mm:ss');
  500. ERT = ET / (k/fitting.iteration) - ET;
  501. fprintf('Iteration #%6d, Elapsed time (hh:mm:ss):%s, Estimated remaining time (hh:mm:ss):%s \n',k,string(ET),string(ERT));
  502. end
  503. end
  504. end
  505. xPosterior = this.array2struct_wglobal(xPosterior_ND,xPosterior_global,fitting.modelParams,isGlobal);
  506. for kvar = 1:Nvar; xPosterior.(fitting.modelParams{kvar}) = shiftdim(xPosterior.(fitting.modelParams{kvar}),1); end
  507. disp('The block affine-invariant ensemble MCMC sampling is completed.')
  508. end
  509. % ---- GLOBAL block: one GW stretch update of phi, locals held fixed -------
  510. function [xCurr_global,logPCurr] = gw_global_block(this, ...
  511. xCurr_ND,xCurr_global,logPCurr,weights,y,Nm,Nv,Nwalker, ...
  512. StepSize,NGlobal,lb_global,ub_global,fitting,isGlobal,modelFWD,varargin)
  513. useRedBlack = strcmpi(fitting.Ensembleupdate,'redblack');
  514. if ~useRedBlack
  515. % --- simultaneous (default, original behaviour) ---
  516. partner = this.find_partner(Nwalker);
  517. zz_g = ((StepSize-1)*rand(1,1,Nwalker,'like',xCurr_global) + 1).^2 / StepSize;
  518. gProp = xCurr_global(:,:,partner) + (xCurr_global - xCurr_global(:,:,partner)).*zz_g;
  519. oob_g = max(or(gProp<lb_global, gProp>ub_global),[],1);
  520. gProp = min(max(gProp,lb_global),ub_global);
  521. s_glob = this.array2struct_wglobal(xCurr_ND, gProp, fitting.modelParams, isGlobal);
  522. logP_g = arrayfun(@logP_Gaussian, sum( weights.*(modelFWD(s_glob,varargin{:})-y).^2, 1 ), s_glob.noise, Nm);
  523. dlogP = sum( double(logP_g - logPCurr), 2 );
  524. aR_g = min( zz_g.^(NGlobal-1).*exp(dlogP), 1 );
  525. acc_g = aR_g > rand(1,1,Nwalker,'like',xCurr_global);
  526. acc_g(oob_g) = 0; acc_g = logical(acc_g);
  527. xCurr_global(:,:,acc_g) = gProp(:,:,acc_g);
  528. logPCurr(:,:,acc_g) = logP_g(:,:,acc_g);
  529. else
  530. % --- red-black: two sub-steps over walker halves ---
  531. h_g = floor(Nwalker/2); halfIdx_g = {1:h_g, h_g+1:Nwalker};
  532. for s = 1:2
  533. active = halfIdx_g{s}; frozen = halfIdx_g{3-s}; na = numel(active);
  534. pidx = frozen(randi(numel(frozen),1,na));
  535. xg_act = xCurr_global(:,:,active);
  536. zz_g = ((StepSize-1)*rand(1,1,na,'like',xCurr_global) + 1).^2 / StepSize;
  537. gProp = xCurr_global(:,:,pidx) + (xg_act - xCurr_global(:,:,pidx)).*zz_g;
  538. oob_g = max(or(gProp<lb_global(:,:,active), gProp>ub_global(:,:,active)),[],1);
  539. gProp = min(max(gProp,lb_global(:,:,active)),ub_global(:,:,active));
  540. % eval at (current local active, new global active) -- locals fixed this block
  541. s_glob = this.array2struct_wglobal(xCurr_ND(:,:,active), gProp, fitting.modelParams, isGlobal);
  542. logP_g = arrayfun(@logP_Gaussian, sum( weights(:,:,active).*(modelFWD(s_glob,varargin{:})-y).^2, 1 ), s_glob.noise, Nm);
  543. lp_act = logPCurr(:,:,active);
  544. dlogP = sum( double(logP_g - lp_act), 2 ); % [1,1,na], double
  545. aR_g = min( zz_g.^(NGlobal-1).*exp(dlogP), 1 );
  546. acc_g = aR_g > rand(1,1,na,'like',xCurr_global);
  547. acc_g(oob_g) = 0; acc_g = logical(acc_g);
  548. xg_act(:,:,acc_g) = gProp(:,:,acc_g);
  549. lp_act(:,:,acc_g) = logP_g(:,:,acc_g);
  550. xCurr_global(:,:,active) = xg_act;
  551. logPCurr(:,:,active) = lp_act;
  552. end
  553. end
  554. end
  555. end
  556. methods(Static)
  557. % check and set default fitting algorithm parameters
  558. function fitting2 = check_set_default_basic(fitting)
  559. % Input
  560. % -----
  561. % fitting : structure contains fitting algorithm parameters
  562. % .iteration : no. of maximum MCMC iterations, default = 200k
  563. % .repetition : no. of MCMC repetitions, default = 1
  564. % .thinning : MCMC thinning interval, default = every 20 iterations
  565. % .burnin : MCMC burn-in ratio, default = 10%
  566. % .metric : method to compute expected valur from posterior distribution, 'mean' (default) | 'median'
  567. % .algorithm : MCMC algorithm 'MH': Metropolis-Hastings; 'GW': Goodman-Weare, 'MH' (default) | 'GW'
  568. % .StepSize : Step size for Goodman-Weare, default = 2
  569. % .Nwalker : number of walkers for Goodman-Weare, default = 50
  570. %
  571. fitting2 = fitting;
  572. % get fitting algorithm setting
  573. if ~isfield(fitting,'iteration'); fitting2.iteration = 2e5; end
  574. if ~isfield(fitting,'thinning'); fitting2.thinning = 20; end % thinning, sampled every 100 interval
  575. if ~isfield(fitting,'metric'); fitting2.metric = {'mean','std'}; end
  576. if ~isfield(fitting,'burnin'); fitting2.burnin = 0.1; end % 10% burnin
  577. if ~isfield(fitting,'repetition'); fitting2.repetition = 1; end
  578. if ~isfield(fitting,'outputFilename'); fitting2.outputFilename = []; end
  579. if ~isfield(fitting,'algorithm'); fitting2.algorithm = 'MH'; end
  580. if ~isfield(fitting,'StepSize'); fitting2.StepSize = 2; end
  581. if ~isfield(fitting,'Nwalker'); fitting2.Nwalker = 50; end
  582. if ~isfield(fitting,'ub'); fitting2.ub = []; end
  583. if ~isfield(fitting,'lb'); fitting2.lb = []; end
  584. if ~isfield(fitting,'startRange'); fitting2.startRange = 0.001; end
  585. % --- choose ensemble update scheme (default = original behaviour) ----
  586. if ~isfield(fitting,'Ensembleupdate'); fitting2.Ensembleupdate = 'simultaneous'; end
  587. if ~isfield(fitting,'globalWarmup'); fitting2.globalWarmup = 0; end
  588. if any(ismember(fitting2.metric,'mode'))
  589. if ~isfield(fitting,'Nbin'); fitting2.Nbin = 1001; end
  590. end
  591. if ~isfield(fitting,'autoMemManage'); fitting2.autoMemManage = true; end
  592. if ~iscell(fitting2.metric)
  593. fitting2.metric = cellstr(fitting2.metric);
  594. end
  595. if strcmpi(fitting2.algorithm ,'gw'); fitting2.algorithm = 'ensemble'; end % legacy
  596. end
  597. % display fitting algorithm parameters
  598. function display_basic_algorithm_parameters(fitting)
  599. if strcmpi( fitting.algorithm, 'ensemble'); algorithm = 'Affine-Invariant Ensemble';
  600. else; algorithm = 'Metropolis-Hastings'; end
  601. disp('----------------------------------------------------');
  602. disp('Markov Chain Monte Carlo (MCMC) algorithm parameters');
  603. disp('----------------------------------------------------');
  604. disp(['Algorithm : ', algorithm]);
  605. disp(['No. of iterations : ', num2str(fitting.iteration)]);
  606. disp(['No. of repetitions: ', num2str(fitting.repetition)])
  607. disp(['Thinning : ', num2str(fitting.thinning)]);
  608. disp(['Burn-in (#iter.) : ' num2str(mcmc.get_number_burnin(fitting))])
  609. disp(['Metric(s) : ', cell2str(fitting.metric)]);
  610. if strcmpi( fitting.algorithm, 'ensemble'); disp(['Step size : ', num2str(fitting.StepSize) ]); end
  611. if strcmpi( fitting.algorithm, 'ensemble'); disp(['No. of walkers : ', num2str(fitting.Nwalker) ]); end
  612. end
  613. % save the mcmc output structure variable into disk space
  614. function save_mcmc_output(outputFilename,out)
  615. % Input
  616. % ------------------
  617. % outputFilename : output filename
  618. % out : output structure of askadam
  619. %
  620. % save the estimation results if the output filename is provided
  621. if ~isempty(outputFilename)
  622. [output_dir,~,~] = fileparts(outputFilename);
  623. if ~exist(output_dir,'dir')
  624. mkdir(output_dir);
  625. end
  626. save(outputFilename,'out');
  627. fprintf('Estimation output is saved at %s\n',outputFilename);
  628. end
  629. end
  630. % convert numerical array into structure variable for FWD function
  631. function x_struct = array2struct(x,fields)
  632. for k = 1:numel(fields)
  633. x_struct.(fields{k}) = x(k,:,:,:,:,:);
  634. end
  635. end
  636. % convert structure variable into numerical array
  637. function x = struct2array(x_struct,fields)
  638. nVol = size(x_struct.(fields{1}),2);
  639. nWalker = size(x_struct.(fields{1}),3);
  640. x = gpuArray(zeros(numel(fields),nVol,nWalker,"single"));
  641. for k = 1:numel(fields)
  642. x(k,:,:) = x_struct.(fields{k});
  643. end
  644. end
  645. function x_struct = array2struct_wglobal(x_ND,x_global,fields, isGlobal)
  646. ctr_global = 1; ctr_ND = 1;
  647. for k = 1:numel(fields)
  648. if isGlobal(k)
  649. x_struct.(fields{k}) = x_global(ctr_global,:,:,:,:,:);
  650. ctr_global = ctr_global + 1;
  651. else
  652. x_struct.(fields{k}) = x_ND(ctr_ND,:,:,:,:,:);
  653. ctr_ND = ctr_ND +1;
  654. end
  655. end
  656. end
  657. function [x_ND,x_global] = struct2array_wglobal(x_struct,fields)
  658. % check if the fitting parameter is global or voxel
  659. isGlobal = mcmc.check_global_constant(x_struct,fields);
  660. nVol = 0; for k = 1:numel(fields); nVol = max(size(x_struct.(fields{k}),2),nVol); end
  661. nWalker = size(x_struct.(fields{1}),3);
  662. x_ND = gpuArray(zeros(numel(isGlobal(isGlobal==0)),nVol,nWalker,"single"));
  663. x_global = gpuArray(zeros(numel(isGlobal(isGlobal==1)),1,nWalker,"single"));
  664. ctr_global = 1; ctr_ND = 1;
  665. for k = 1:numel(fields)
  666. if isGlobal(k)
  667. x_global(ctr_global,1,:) = x_struct.(fields{k});
  668. ctr_global = ctr_global +1;
  669. else
  670. x_ND(ctr_ND,:,:) = x_struct.(fields{k});
  671. ctr_ND = ctr_ND +1;
  672. end
  673. end
  674. end
  675. function isGlobal = check_global_constant(x_struct,fields)
  676. % check if the fitting parameter is global or voxel
  677. isGlobal = zeros(numel(fields),1);
  678. for k = 1:numel(fields)
  679. if size(x_struct.(fields{k}),2) > 1
  680. isGlobal(k) = false;
  681. else
  682. isGlobal(k) = true;
  683. end
  684. end
  685. end
  686. % compute the number of iteration requires for burn-in
  687. function Nburnin = get_number_burnin(fitting)
  688. if fitting.burnin < 1
  689. Nburnin = floor(fitting.iteration*fitting.burnin);
  690. else
  691. Nburnin = fitting.burnin;
  692. end
  693. end
  694. % find a unique partner for an index
  695. function partner = find_partner(maxIndex)
  696. isSelfPartner = true;
  697. while isSelfPartner
  698. partner = randperm(maxIndex);
  699. isSelfPartner = any(partner == 1:maxIndex,'all');
  700. end
  701. end
  702. % convert estimation into organised output structure
  703. function out = res2out(xPosterior,fitting,mask)
  704. % store the unshaped posterior into out
  705. out.posterior = xPosterior;
  706. % compute additional metric if specified
  707. fields = fieldnames(xPosterior);
  708. % Nvox = size(xPosterior.(fields{1}),1);
  709. Nsample = prod(size(xPosterior.(fields{1}),2:5));
  710. metrics = fitting.metric;
  711. if ~isempty(metrics)
  712. for km = 1:numel(metrics)
  713. switch lower(metrics{km})
  714. case 'mean'
  715. for kvar=1:numel(fields)
  716. tmp = mean( reshape( xPosterior.(fields{kvar}), [size(xPosterior.(fields{kvar}),1), Nsample]),2);
  717. tmp = utils.reshape_ND2image(tmp,mask);
  718. out.mean.(fields{kvar}) = tmp;
  719. end
  720. case 'median'
  721. for kvar=1:numel(fields)
  722. tmp = median( reshape( xPosterior.(fields{kvar}), [size(xPosterior.(fields{kvar}),1), Nsample]),2);
  723. tmp = utils.reshape_ND2image(tmp,mask);
  724. out.median.(fields{kvar}) = tmp;
  725. end
  726. case 'std'
  727. for kvar=1:numel(fields)
  728. tmp = std( reshape( xPosterior.(fields{kvar}), [size(xPosterior.(fields{kvar}),1), Nsample]),[],2);
  729. tmp = utils.reshape_ND2image(tmp,mask);
  730. out.std.(fields{kvar}) = tmp;
  731. end
  732. case 'iqr'
  733. for kvar=1:numel(fields)
  734. tmp = iqr( reshape( xPosterior.(fields{kvar}), [size(xPosterior.(fields{kvar}),1), Nsample]),2);
  735. tmp = utils.reshape_ND2image(tmp,mask);
  736. out.iqr.(fields{kvar}) = tmp;
  737. end
  738. case 'mode'
  739. for kvar=1:numel(fields)
  740. Nbin = fitting.Nbin;
  741. idx = find(ismember(fitting.modelParams,fields{kvar}));
  742. edges = linspace(fitting.lb(idx)-1e-8,fitting.ub(idx)+1e-8,Nbin);
  743. tmp = reshape( xPosterior.(fields{kvar}), [size(xPosterior.(fields{kvar}),1), Nsample]);
  744. tmp = mode(discretize(tmp,edges),2);
  745. tmp = (edges(tmp) + edges(tmp+1)) / 2;
  746. tmp = utils.reshape_ND2image(tmp.',mask);
  747. out.mode.(fields{kvar}) = tmp;
  748. end
  749. end
  750. end
  751. end
  752. end
  753. % make sure all network parameters stay between 0 and 1
  754. function parameters = set_boundary(parameters,ub,lb)
  755. field = fieldnames(parameters);
  756. for k = 1:numel(field)
  757. parameters.(field{k}) = max(parameters.(field{k}),lb(k)); % Lower bound
  758. parameters.(field{k}) = min(parameters.(field{k}),ub(k)); % upper bound
  759. end
  760. end
  761. end
  762. end

mcmc.m at commit 05f49d4, under GPL-3.0 · at the source

Overview

Authors: Yixin Ma1,2, Laleh Eskandarian1,2, Kwok‐Shing Chan1,2, Hansol Lee1,2,3, Gabriel Ramos‐Llordén1,2, Aneri Bhatt1, Julianna Gerold1, Mirsad Mahmutovic4, Boris Keil4,5, Hong‐Hsi Lee1,2, Susie Y Huang1,2,6
  1. Athinoula A. Martinos Center for Biomedical Imaging, Department of Radiology, Massachusetts General Hospital, Charlestown, Massachusetts, USA
  2. Harvard Medical School, Boston, Massachusetts, USA
  3. Department of Biomedical Engineering, Ulsan National Institute of Science and Technology, Ulsan, South Korea
  4. Institute of Medical Physics and Radiation Protection, Mittelhessen University of Applied Sciences, Giessen, Germany
  5. Department of Diagnostic and Interventional Radiology, University Hospital Marburg, Philipps University of Marburg, Marburg, Germany
  6. Harvard‐MIT Division of Health Sciences and Technology, Massachusetts Institute of Technology, Cambridge, Massachusetts, USA
Journal: Human brain mapping, volume 47, issue 8, article e70553
Dates: received 29 August 2025; accepted 30 April 2026; published online 4 June 2026; in print June 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1002/hbm.70553 · PMID 42240067 · PMCID PMC13266422 · OpenAlex W7163552186
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: structural MRI / diffusion (modality), human (organism)
Methods: Spectral & time-frequency, Statistics, Connectivity, fMRI & imaging
MeSH: Axons*, Brain*, Connectome*, Diffusion Magnetic Resonance Imaging*, Adult, Female, Humans, Male, Middle Aged, Young Adult (* major topic)
Topic: Advanced Neuroimaging Techniques and Applications (Radiology, Nuclear Medicine and Imaging, Medicine), according to OpenAlex
Funding: National Institute on Aging (R21AG085795, K99AG073506); NIBIB NIH HHS (U01EB026996-S1, P41EB030, P41EB015896, U01EB026996); National Institute of Biomedical Imaging and Bioengineering (U01EB026996‐S1, U01EB026996, P41EB030, P41EB015896); NINDS NIH HHS (U24NS137077, R01NS118187); Office of the Director (OD) of the National Institutes of Health (S10OD032184); ZonMw Rubicon Fellowship (04520232330012); Office of the Director (OD) of the NIH in partnership with the National Institute of Dental & Craniofacial Research (NIDCR) (DP5OD031854); NIA NIH HHS (K99AG073506, R21AG085795); National Institute of Neurological Disorders and Stroke (U24NS137077, R01NS118187); ZonMw (04520232330012); NIH/NINDS Pathway-to-Independence Award (K99NS132984); National Research Foundation of Korea (NRF) (RS-2024-00411768)
Citations: not cited yet (Europe PMC); 89 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repositories

Its files are read in the Code ↔ Paper reader above, with 5 matches between paragraphs and lines of code.

yixinma9/diffusion_preproc_C2

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 59032d4f5ea70d3fbe37d93ae8d4d049e217d315, 7 August 2025
Languages: Python (15), Shell (1)
Size: 29 files, 16 scripts
Software Heritage: not archived
Found in: the text, “Image Preprocessing”
Holds: README, environment (requirements.txt)
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NiBabel (4 files), MRtrix3 (3 files), NumPy (3 files), FSL (2 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
17 files

kschan0214/gacelle

License: GPL-3.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 05f49d48dac7926b6c69a5612ec5dfca7f8cee1b, 9 September 2026
Languages: MATLAB (210), Python (1)
Size: 276 files, 211 scripts
Software Heritage: not archived
Found in: the text, “Markov Chain Monte Carlo With Ensemble Sampler W”
Holds: README, license file, environment (docs/requirements.txt), tests, continuous integration, documentation
Not found: CITATION.cff
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
213 files

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 227 scripts, each with its path and the digest of its content;
  • 5 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data availability statement

The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

  • it says that the data are available on request

Read it in the paper: doi.org/10.1002/hbm.70553.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 11 authors, 10 MeSH terms, 12 funders, 87 references.

Cite

This paper

Ma, Y., Eskandarian, L., Chan, K., Lee, H., Ramos‐Llordén, G., Bhatt, A., Gerold, J., Mahmutovic, M., Keil, B., Lee, H., & Huang, S. Y. (2026). Axon Diameter Mapping in the Living Human Brain with Ultra-High-Gradient Diffusion MRI at 500 mT/m Gradient Strength. Human brain mapping, 47(8), e70553. https://doi.org/10.1002/hbm.70553

BibTeX

@article{ma2026axon,
author = {Ma, Yixin and Eskandarian, Laleh and Chan, Kwok‐Shing and Lee, Hansol and Ramos‐Llordén, Gabriel and Bhatt, Aneri and Gerold, Julianna and Mahmutovic, Mirsad and Keil, Boris and Lee, Hong‐Hsi and Huang, Susie Y},
title = {{Axon Diameter Mapping in the Living Human Brain with Ultra-High-Gradient Diffusion MRI at 500 mT/m Gradient Strength}},
journal = {Human brain mapping},
year = {2026},
month = jun,
volume = {47},
number = {8},
pages = {e70553},
publisher = {Wiley},
issn = {1065-9471},
doi = {10.1002/hbm.70553},
url = {https://doi.org/10.1002/hbm.70553},
pmid = {42240067},
pmcid = {PMC13266422}
}

RIS

TY - JOUR
AU - Ma, Yixin
AU - Eskandarian, Laleh
AU - Chan, Kwok‐Shing
AU - Lee, Hansol
AU - Ramos‐Llordén, Gabriel
AU - Bhatt, Aneri
AU - Gerold, Julianna
AU - Mahmutovic, Mirsad
AU - Keil, Boris
AU - Lee, Hong‐Hsi
AU - Huang, Susie Y
TI - Axon Diameter Mapping in the Living Human Brain with Ultra-High-Gradient Diffusion MRI at 500 mT/m Gradient Strength
T2 - Human brain mapping
J2 - Hum Brain Mapp
PY - 2026
DA - 2026/06/01
VL - 47
IS - 8
SP - e70553
SN - 1065-9471
PB - Wiley
DO - 10.1002/hbm.70553
UR - https://doi.org/10.1002/hbm.70553
LA - en
ER -

CSL-JSON

{
"id": "10.1002/hbm.70553",
"type": "article-journal",
"title": "Axon Diameter Mapping in the Living Human Brain with Ultra-High-Gradient Diffusion MRI at 500 mT/m Gradient Strength",
"container-title": "Human brain mapping",
"author": [
{
"family": "Ma",
"given": "Yixin"
},
{
"family": "Eskandarian",
"given": "Laleh"
},
{
"family": "Chan",
"given": "Kwok‐Shing"
},
{
"family": "Lee",
"given": "Hansol"
},
{
"family": "Ramos‐Llordén",
"given": "Gabriel"
},
{
"family": "Bhatt",
"given": "Aneri"
},
{
"family": "Gerold",
"given": "Julianna"
},
{
"family": "Mahmutovic",
"given": "Mirsad"
},
{
"family": "Keil",
"given": "Boris"
},
{
"family": "Lee",
"given": "Hong‐Hsi"
},
{
"family": "Huang",
"given": "Susie Y"
}
],
"container-title-short": "Hum Brain Mapp",
"volume": "47",
"issue": "8",
"page": "e70553",
"DOI": "10.1002/hbm.70553",
"PMID": "42240067",
"PMCID": "PMC13266422",
"ISSN": "1065-9471",
"publisher": "Wiley",
"URL": "https://doi.org/10.1002/hbm.70553",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
1
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1109/tmi.2026.3664328 [code]
Axon Diameter Mapping From Myelin Water Diffusion MRI.
Journal: IEEE transactions on medical imaging
In common: MRtrix3, Optimization Toolbox, Image Processing Toolbox, 1 other tool, structural MRI / diffusion, 23 references, 2 authors
[2] doi:10.1002/mrm.70490 [code]
Dependence of the Extra-Cellular Diffusion Coefficient on the Fractions of Neurites and Cell Bodies in Gray Matter.
Journal: Magnetic resonance in medicine
In common: structural MRI / diffusion, 17 references, author Hansol Lee
[3] doi:10.1002/mrm.70378 [code]
Investigating the Sensitivity of the Diffusion MRI Signal to Magnetization Transfer and Permeability via Monte-Carlo Simulations.
Journal: Magnetic resonance in medicine
In common: FSL, NumPy, structural MRI / diffusion, 16 references
[4] doi:10.1371/journal.pbio.3003861
Learning engages transient and sustained cellular mechanisms in the human brain.
Journal: PLoS biology
In common: structural MRI / diffusion, 12 references
[5] doi:10.1038/s41467-026-71151-2 [code]
Common and distinct neural correlates of social interaction processing and theory of mind in narratives.
Journal: Nature communications
In common: MRtrix3, Optimization Toolbox, Parallel Computing Toolbox, 6 other tools, 1 reference
[6] doi:10.1162/imag.a.1317 [code]
Bayesian insights into exchange and restriction in gray matter diffusion MRI.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: NiBabel, NumPy, structural MRI / diffusion, 8 references
[7] doi:10.1038/s41598-026-51531-w [code]
Multimodal age-dependent diffusion-MRI analysis of the neocortex in a rat model of cortical dysplasia.
Journal: Scientific reports
In common: MRtrix3, FSL, Image Processing Toolbox, 3 other tools, structural MRI / diffusion, 4 references
[8] doi:10.7554/elife.107661 [code]
In vivo mapping of striatal neurodegeneration in Huntington's disease with Soma and Neurite Density Imaging.
Journal: eLife
In common: Deep Learning Toolbox, Optimization Toolbox, Image Processing Toolbox, 1 other tool, structural MRI / diffusion, 4 references
[9] doi:10.1038/s43856-026-01614-6 [code]
Simulation-based inference at the theoretical limit for fast, robust microstructural MRI with minimal diffusion data.
Journal: Communications medicine
In common: NumPy, structural MRI / diffusion, 7 references
[10] doi:10.1038/s41586-026-10631-3 [code]
A prognostic human brain network for diffuse midline glioma.
Journal: Nature
In common: Optimization Toolbox, Parallel Computing Toolbox, FreeSurfer, 5 other tools, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.