source: sasmodels/doc/genmodel.py @ a42b091

Last change on this file since a42b091 was a42b091, checked in by Paul Kienzle <pkienzle@…>, 7 months ago

suggest a way to do syntax highlighting

  • Property mode set to 100644
File size: 8.7 KB
Line 
1from __future__ import print_function
2
3import sys, os, math, re
4import numpy as np
5import matplotlib
6matplotlib.use('Agg')
7import matplotlib.pyplot as plt
8sys.path.insert(0, os.path.abspath('..'))
9import sasmodels
10from sasmodels import generate, core
11from sasmodels.direct_model import DirectModel, call_profile
12from sasmodels.data import empty_data1D, empty_data2D
13
14try:
15    from typing import Dict, Any
16except ImportError:
17    pass
18else:
19    from matplotlib.axes import Axes
20    from sasmodels.kernel import KernelModel
21    from sasmodels.modelinfo import ModelInfo
22
23
24def plot_1d(model, opts, ax):
25    # type: (KernelModel, Dict[str, Any], Axes) -> None
26    """
27    Create a 1-D image.
28    """
29    q_min, q_max, nq = opts['q_min'], opts['q_max'], opts['nq']
30    q_min = math.log10(q_min)
31    q_max = math.log10(q_max)
32    q = np.logspace(q_min, q_max, nq)
33    data = empty_data1D(q)
34    calculator = DirectModel(data, model)
35    Iq1D = calculator()
36
37    ax.plot(q, Iq1D, color='blue', lw=2, label=model.info.name)
38    ax.set_xlabel(r'$Q \/(\AA^{-1})$')
39    ax.set_ylabel(r'$I(Q) \/(\mathrm{cm}^{-1})$')
40    ax.set_xscale(opts['xscale'])
41    ax.set_yscale(opts['yscale'])
42    #ax.legend(loc='best')
43
44def plot_2d(model, opts, ax):
45    # type: (KernelModel, Dict[str, Any], Axes) -> None
46    """
47    Create a 2-D image.
48    """
49    qx_max, nq2d = opts['qx_max'], opts['nq2d']
50    q = np.linspace(-qx_max, qx_max, nq2d) # type: np.ndarray
51    data2d = empty_data2D(q, resolution=0.0)
52    calculator = DirectModel(data2d, model)
53    Iq2D = calculator() #background=0)
54    Iq2D = Iq2D.reshape(nq2d, nq2d)
55    if opts['zscale'] == 'log':
56        Iq2D = np.log(np.clip(Iq2D, opts['vmin'], np.inf))
57    ax.imshow(Iq2D, interpolation='nearest', aspect=1, origin='lower',
58              extent=[-qx_max, qx_max, -qx_max, qx_max], cmap=opts['colormap'])
59    ax.set_xlabel(r'$Q_x \/(\AA^{-1})$')
60    ax.set_ylabel(r'$Q_y \/(\AA^{-1})$')
61
62def plot_profile_inset(model_info, ax):
63    p = ax.get_position()
64    width, height = 0.4*(p.x1-p.x0), 0.4*(p.y1-p.y0)
65    left, bottom = p.x1-width, p.y1-height
66    inset = plt.gcf().add_axes([left, bottom, width, height])
67    x, y, labels = call_profile(model_info)
68    inset.plot(x, y, '-')
69    inset.locator_params(nbins=4)
70    #inset.set_xlabel(labels[0])
71    #inset.set_ylabel(labels[1])
72    inset.text(0.99, 0.99, "profile",
73               horizontalalignment="right",
74               verticalalignment="top",
75               transform=inset.transAxes)
76
77def figfile(model_info):
78    # type: (ModelInfo) -> str
79    return model_info.id + '_autogenfig.png'
80
81def make_figure(model_info, opts):
82    # type: (ModelInfo, Dict[str, Any]) -> None
83    """
84    Generate the figure file to include in the docs.
85    """
86    model = core.build_model(model_info)
87
88    fig_height = 3.0 # in
89    fig_left = 0.6 # in
90    fig_right = 0.5 # in
91    fig_top = 0.6*0.25 # in
92    fig_bottom = 0.6*0.75
93    if model_info.parameters.has_2d:
94        plot_height = fig_height - (fig_top+fig_bottom)
95        plot_width = plot_height
96        fig_width = 2*(plot_width + fig_left + fig_right)
97        aspect = (fig_width, fig_height)
98        ratio = aspect[0]/aspect[1]
99        ax_left = fig_left/fig_width
100        ax_bottom = fig_bottom/fig_height
101        ax_height = plot_height/fig_height
102        ax_width = ax_height/ratio # square axes
103        fig = plt.figure(figsize=aspect)
104        ax2d = fig.add_axes([0.5+ax_left, ax_bottom, ax_width, ax_height])
105        plot_2d(model, opts, ax2d)
106        ax1d = fig.add_axes([ax_left, ax_bottom, ax_width, ax_height])
107        plot_1d(model, opts, ax1d)
108        #ax.set_aspect('square')
109    else:
110        plot_height = fig_height - (fig_top+fig_bottom)
111        plot_width = (1+np.sqrt(5))/2*fig_height
112        fig_width = plot_width + fig_left + fig_right
113        ax_left = fig_left/fig_width
114        ax_bottom = fig_bottom/fig_height
115        ax_width = plot_width/fig_width
116        ax_height = plot_height/fig_height
117        aspect = (fig_width, fig_height)
118        fig = plt.figure(figsize=aspect)
119        ax1d = fig.add_axes([ax_left, ax_bottom, ax_width, ax_height])
120        plot_1d(model, opts, ax1d)
121
122    if model_info.profile:
123        plot_profile_inset(model_info, ax1d)
124
125    # Save image in model/img
126    path = os.path.join('model', 'img', figfile(model_info))
127    plt.savefig(path, bbox_inches='tight')
128    #print("figure saved in",path)
129
130def copy_if_newer(src, dst):
131    from os.path import dirname, exists, getmtime
132    import shutil
133    if not exists(dst):
134        path = dirname(dst)
135        if not exists(path):
136            os.makedirs(path)
137        shutil.copy2(src, dst)
138    elif getmtime(src) > getmtime(dst):
139        shutil.copy2(src, dst)
140
141def link_sources(model_info):
142    from os.path import basename, dirname, realpath, join as joinpath
143
144    # List source files in reverse order of dependency.
145    model_file = basename(model_info.filename)
146    sources = list(reversed(model_info.source + [model_file]))
147
148    # Copy files to src dir under models directory.  Need to do this
149    # because sphinx can't link to an absolute path.
150    root = dirname(dirname(realpath(__file__)))
151    src = joinpath(root, "sasmodels", "models")
152    dst = joinpath(root, "doc", "model", "src")
153    for path in sources:
154        copy_if_newer(joinpath(src, path), joinpath(dst, path))
155
156    # Link to local copy of the files
157    downloads = [":download:`%s <src/%s>`"%(path, path) for path in sources]
158
159    # Could do syntax highlighting on the model files by creating a rst file
160    # beside each source file named containing source file with
161    #
162    #    src/path.rst:
163    #
164    #    .. {{ path.replace('/','_') }}:
165    #
166    #    .. literalinclude:: {{ src/path }}
167    #        :language: {{ "python" if path.endswith('.py') else "c" }}
168    #        :linenos:
169    #
170    # and link to it using
171    #
172    #     colors = [":ref:`%s`"%(path.replace('/','_')) for path in sources]
173    #
174    # Probably need to dump all the rst files into an index.rst to build them.
175
176    # Link to github repo (either the tagged sasmodels version or master)
177    url = "https://github.com/SasView/sasmodels/blob/v%s"%sasmodels.__version__
178    #url = "https://github.com/SasView/sasmodels/blob/master"%sasmodels.__version__
179    links = ["`%s <%s/sasmodels/models/%s>`_"%(path, url, path) for path in sources]
180
181    sep = u"\n\\ \u25E6 \\ "  # bullet
182    body = "\n**Source**\n"
183    #body += "\n\\ " + sep.join(links) + "\n\n"
184    body += "\n\\ " + sep.join(downloads) + "\n\n"
185    return body
186
187def gen_docs(model_info):
188    # type: (ModelInfo) -> None
189    """
190    Generate the doc string with the figure inserted before the references.
191    """
192
193    # Load the doc string from the module definition file and store it in rst
194    docstr = generate.make_doc(model_info)
195
196    # Auto caption for figure
197    captionstr = '\n'
198    captionstr += '.. figure:: img/' + figfile(model_info) + '\n'
199    captionstr += '\n'
200    if model_info.parameters.has_2d:
201        captionstr += '    1D and 2D plots corresponding to the default parameters of the model.\n'
202    else:
203        captionstr += '    1D plot corresponding to the default parameters of the model.\n'
204    captionstr += '\n'
205
206    # Add figure reference and caption to documentation (at end, before References)
207    pattern = '\*\*REFERENCE'
208    match = re.search(pattern, docstr.upper())
209
210    sources = link_sources(model_info)
211
212    insertion = captionstr + sources
213
214    if match:
215        docstr1 = docstr[:match.start()]
216        docstr2 = docstr[match.start():]
217        docstr = docstr1 + insertion + docstr2
218    else:
219        print('------------------------------------------------------------------')
220        print('References NOT FOUND for model: ', model_info.id)
221        print('------------------------------------------------------------------')
222        docstr += insertion
223
224    open(sys.argv[2],'w').write(docstr)
225
226def process_model(path):
227    # type: (str) -> None
228    """
229    Generate doc file and image file for the given model definition file.
230    """
231
232    # Load the model file
233    model_name = os.path.basename(path)[:-3]
234    model_info = core.load_model_info(model_name)
235
236    # Plotting ranges and options
237    opts = {
238        'xscale'    : 'log',
239        'yscale'    : 'log' if not model_info.structure_factor else 'linear',
240        'zscale'    : 'log' if not model_info.structure_factor else 'linear',
241        'q_min'     : 0.001,
242        'q_max'     : 1.0,
243        'nq'        : 1000,
244        'nq2d'      : 1000,
245        'vmin'      : 1e-3,  # floor for the 2D data results
246        'qx_max'    : 0.5,
247        #'colormap'  : 'gist_ncar',
248        'colormap'  : 'nipy_spectral',
249        #'colormap'  : 'jet',
250    }
251
252    # Generate the RST file and the figure.  Order doesn't matter.
253    gen_docs(model_info)
254    make_figure(model_info, opts)
255
256if __name__ == "__main__":
257    process_model(sys.argv[1])
Note: See TracBrowser for help on using the repository browser.