Update docs

This commit is contained in:
2024-12-10 12:14:36 -06:00
parent 184af2a869
commit 112963c178
37 changed files with 15544 additions and 3662 deletions

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en" data-content_root="../../">
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>6. Training Data Collectors &#8212; MIPLearn 0.4</title>
<link href="../../_static/css/theme.css" rel="stylesheet" />
@@ -22,21 +22,21 @@
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css?v=362ab14a" />
<link rel="stylesheet" type="text/css" href="../../_static/sphinx-book-theme.acff12b8f9c144ce68a297486a2fa670.css?v=b0dfe17c" />
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css?v=2aa19091" />
<link rel="stylesheet" type="text/css" href="../../_static/custom.css?v=f8244a84" />
<link rel="stylesheet" href="../../_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="../../_static/sphinx-book-theme.acff12b8f9c144ce68a297486a2fa670.css" type="text/css" />
<link rel="stylesheet" type="text/css" href="../../_static/nbsphinx-code-cells.css" />
<link rel="stylesheet" type="text/css" href="../../_static/custom.css" />
<link rel="preload" as="script" href="../../_static/js/index.1c5a1a01449ed65a7b51.js">
<script src="../../_static/documentation_options.js?v=751a5dd3"></script>
<script src="../../_static/doctools.js?v=888ff710"></script>
<script src="../../_static/sphinx_highlight.js?v=dc90522c"></script>
<script id="documentation_options" data-url_root="../../" src="../../_static/documentation_options.js"></script>
<script src="../../_static/jquery.js"></script>
<script src="../../_static/underscore.js"></script>
<script src="../../_static/doctools.js"></script>
<script crossorigin="anonymous" integrity="sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA=" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js"></script>
<script src="../../_static/sphinx-book-theme.12a9622fbb08dcb3a2a40b2c02b83a57.js?v=7c4c3336"></script>
<script src="../../_static/sphinx-book-theme.12a9622fbb08dcb3a2a40b2c02b83a57.js"></script>
<script async="async" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="text/x-mathjax-config">MathJax.Hub.Config({"tex2jax": {"inlineMath": [["\\(", "\\)"]], "displayMath": [["\\[", "\\]"]], "processRefs": false, "processEnvironments": false}})</script>
<script>window.MathJax = {"tex": {"inlineMath": [["$", "$"], ["\\(", "\\)"]], "processEscapes": true}, "options": {"ignoreHtmlClass": "tex2jax_ignore|mathjax_ignore|document", "processHtmlClass": "tex2jax_process|mathjax_process|math|output_area"}}</script>
<script defer="defer" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<link rel="index" title="Index" href="../../genindex/" />
<link rel="search" title="Search" href="../../search/" />
<link rel="next" title="7. Feature Extractors" href="../features/" />
@@ -68,7 +68,7 @@
<input type="search" class="form-control" name="q" id="search-input" placeholder="Search the docs ..." aria-label="Search the docs ..." autocomplete="off" >
</form><nav class="bd-links" id="bd-docs-nav" aria-label="Main navigation">
<div class="bd-toc-item active">
<p class="caption" role="heading">
<p class="caption">
<span class="caption-text">
Tutorials
</span>
@@ -95,7 +95,7 @@
</a>
</li>
</ul>
<p class="caption" role="heading">
<p class="caption">
<span class="caption-text">
User Guide
</span>
@@ -127,7 +127,7 @@
</a>
</li>
</ul>
<p class="caption" role="heading">
<p class="caption">
<span class="caption-text">
Python API Reference
</span>
@@ -270,17 +270,17 @@
<div>
<section id="Training-Data-Collectors">
<h1><span class="section-number">6. </span>Training Data Collectors<a class="headerlink" href="#Training-Data-Collectors" title="Link to this heading"></a></h1>
<div class="section" id="Training-Data-Collectors">
<h1><span class="section-number">6. </span>Training Data Collectors<a class="headerlink" href="#Training-Data-Collectors" title="Permalink to this headline"></a></h1>
<p>The first step in solving mixed-integer optimization problems with the assistance of supervised machine learning methods is solving a large set of training instances and collecting the raw training data. In this section, we describe the various training data collectors included in MIPLearn. Additionally, the framework follows the convention of storing all training data in files with a specific data format (namely, HDF5). In this section, we briefly describe this format and the rationale for
choosing it.</p>
<section id="Overview">
<h2><span class="section-number">6.1. </span>Overview<a class="headerlink" href="#Overview" title="Link to this heading"></a></h2>
<div class="section" id="Overview">
<h2><span class="section-number">6.1. </span>Overview<a class="headerlink" href="#Overview" title="Permalink to this headline"></a></h2>
<p>In MIPLearn, a <strong>collector</strong> is a class that solves or analyzes the problem and collects raw data which may be later useful for machine learning methods. Collectors, by convention, take as input: (i) a list of problem data filenames, in gzipped pickle format, ending with <code class="docutils literal notranslate"><span class="pre">.pkl.gz</span></code>; (ii) a function that builds the optimization model, such as <code class="docutils literal notranslate"><span class="pre">build_tsp_model</span></code>. After processing is done, collectors store the training data in a HDF5 file located alongside with the problem data. For example, if
the problem data is stored in file <code class="docutils literal notranslate"><span class="pre">problem.pkl.gz</span></code>, then the collector writes to <code class="docutils literal notranslate"><span class="pre">problem.h5</span></code>. Collectors are, in general, very time consuming, as they may need to solve the problem to optimality, potentially multiple times.</p>
</section>
<section id="HDF5-Format">
<h2><span class="section-number">6.2. </span>HDF5 Format<a class="headerlink" href="#HDF5-Format" title="Link to this heading"></a></h2>
</div>
<div class="section" id="HDF5-Format">
<h2><span class="section-number">6.2. </span>HDF5 Format<a class="headerlink" href="#HDF5-Format" title="Permalink to this headline"></a></h2>
<p>MIPLearn stores all training data in <a class="reference external" href="HDF5">HDF5</a> (Hierarchical Data Format, Version 5) files. The HDF format was originally developed by the <a class="reference external" href="https://en.wikipedia.org/wiki/National_Center_for_Supercomputing_Applications">National Center for Supercomputing Applications</a> (NCSA) for storing and organizing large amounts of data, and supports a variety of data types, including integers, floating-point numbers, strings, and arrays. Compared to other formats, such as CSV, JSON or SQLite, the
HDF5 format provides several advantages for MIPLearn, including:</p>
<ul class="simple">
@@ -292,41 +292,41 @@ HDF5 format provides several advantages for MIPLearn, including:</p>
<p>MIPLearn currently uses HDF5 as simple key-value storage for numerical data; more advanced features of the format, such as metadata, are not currently used. Although files generated by MIPLearn can be read with any HDF5 library, such as <a class="reference external" href="https://www.h5py.org/">h5py</a>, some convenience functions are provided to make the access more simple and less error-prone. Specifically, the class <a class="reference external" href="../../api/helpers/#miplearn.h5.H5File">H5File</a>, which is built on top of h5py, provides the methods
<a class="reference external" href="../../api/helpers/#miplearn.h5.H5File.put_scalar">put_scalar</a>, <a class="reference external" href="../../api/helpers/#miplearn.h5.H5File.put_scalar">put_array</a>, <a class="reference external" href="../../api/helpers/#miplearn.h5.H5File.put_scalar">put_sparse</a>, <a class="reference external" href="../../api/helpers/#miplearn.h5.H5File.put_scalar">put_bytes</a> to store, respectively, scalar values, dense multi-dimensional arrays, sparse multi-dimensional arrays and arbitrary binary data. The corresponding <em>get</em> methods are also provided. Compared to pure h5py methods, these methods
automatically perform type-checking and gzip compression. The example below shows their usage.</p>
<section id="Example">
<h3>Example<a class="headerlink" href="#Example" title="Link to this heading"></a></h3>
<div class="section" id="Example">
<h3>Example<a class="headerlink" href="#Example" title="Permalink to this headline"></a></h3>
<div class="nbinput docutils container">
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[1]:
</pre></div>
</div>
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
<span class="kn">import</span> <span class="nn">scipy.sparse</span>
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span>import numpy as np
import scipy.sparse
<span class="kn">from</span> <span class="nn">miplearn.h5</span> <span class="kn">import</span> <span class="n">H5File</span>
from miplearn.h5 import H5File
<span class="c1"># Set random seed to make example reproducible</span>
<span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
# Set random seed to make example reproducible
np.random.seed(42)
<span class="c1"># Create a new empty HDF5 file</span>
<span class="k">with</span> <span class="n">H5File</span><span class="p">(</span><span class="s2">&quot;test.h5&quot;</span><span class="p">,</span> <span class="s2">&quot;w&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">h5</span><span class="p">:</span>
<span class="c1"># Store a scalar</span>
<span class="n">h5</span><span class="o">.</span><span class="n">put_scalar</span><span class="p">(</span><span class="s2">&quot;x1&quot;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">h5</span><span class="o">.</span><span class="n">put_scalar</span><span class="p">(</span><span class="s2">&quot;x2&quot;</span><span class="p">,</span> <span class="s2">&quot;hello world&quot;</span><span class="p">)</span>
# Create a new empty HDF5 file
with H5File(&quot;test.h5&quot;, &quot;w&quot;) as h5:
# Store a scalar
h5.put_scalar(&quot;x1&quot;, 1)
h5.put_scalar(&quot;x2&quot;, &quot;hello world&quot;)
<span class="c1"># Store a dense array and a dense matrix</span>
<span class="n">h5</span><span class="o">.</span><span class="n">put_array</span><span class="p">(</span><span class="s2">&quot;x3&quot;</span><span class="p">,</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]))</span>
<span class="n">h5</span><span class="o">.</span><span class="n">put_array</span><span class="p">(</span><span class="s2">&quot;x4&quot;</span><span class="p">,</span> <span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">rand</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">))</span>
# Store a dense array and a dense matrix
h5.put_array(&quot;x3&quot;, np.array([1, 2, 3]))
h5.put_array(&quot;x4&quot;, np.random.rand(3, 3))
<span class="c1"># Store a sparse matrix</span>
<span class="n">h5</span><span class="o">.</span><span class="n">put_sparse</span><span class="p">(</span><span class="s2">&quot;x5&quot;</span><span class="p">,</span> <span class="n">scipy</span><span class="o">.</span><span class="n">sparse</span><span class="o">.</span><span class="n">random</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mf">0.5</span><span class="p">))</span>
# Store a sparse matrix
h5.put_sparse(&quot;x5&quot;, scipy.sparse.random(5, 5, 0.5))
<span class="c1"># Re-open the file we just created and print</span>
<span class="c1"># previously-stored data</span>
<span class="k">with</span> <span class="n">H5File</span><span class="p">(</span><span class="s2">&quot;test.h5&quot;</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">h5</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;x1 =&quot;</span><span class="p">,</span> <span class="n">h5</span><span class="o">.</span><span class="n">get_scalar</span><span class="p">(</span><span class="s2">&quot;x1&quot;</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;x2 =&quot;</span><span class="p">,</span> <span class="n">h5</span><span class="o">.</span><span class="n">get_scalar</span><span class="p">(</span><span class="s2">&quot;x2&quot;</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;x3 =&quot;</span><span class="p">,</span> <span class="n">h5</span><span class="o">.</span><span class="n">get_array</span><span class="p">(</span><span class="s2">&quot;x3&quot;</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;x4 =&quot;</span><span class="p">,</span> <span class="n">h5</span><span class="o">.</span><span class="n">get_array</span><span class="p">(</span><span class="s2">&quot;x4&quot;</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;x5 =&quot;</span><span class="p">,</span> <span class="n">h5</span><span class="o">.</span><span class="n">get_sparse</span><span class="p">(</span><span class="s2">&quot;x5&quot;</span><span class="p">))</span>
# Re-open the file we just created and print
# previously-stored data
with H5File(&quot;test.h5&quot;, &quot;r&quot;) as h5:
print(&quot;x1 =&quot;, h5.get_scalar(&quot;x1&quot;))
print(&quot;x2 =&quot;, h5.get_scalar(&quot;x2&quot;))
print(&quot;x3 =&quot;, h5.get_array(&quot;x3&quot;))
print(&quot;x4 =&quot;, h5.get_array(&quot;x4&quot;))
print(&quot;x5 =&quot;, h5.get_sparse(&quot;x5&quot;))
</pre></div>
</div>
</div>
@@ -355,10 +355,10 @@ x5 = (3, 2) 0.6803075671195984
(3, 0) 0.83319491147995
</pre></div></div>
</div>
</section>
</section>
<section id="Basic-collector">
<h2><span class="section-number">6.3. </span>Basic collector<a class="headerlink" href="#Basic-collector" title="Link to this heading"></a></h2>
</div>
</div>
<div class="section" id="Basic-collector">
<h2><span class="section-number">6.3. </span>Basic collector<a class="headerlink" href="#Basic-collector" title="Permalink to this headline"></a></h2>
<p><a class="reference external" href="../../api/collectors/#miplearn.collectors.basic.BasicCollector">BasicCollector</a> is the most fundamental collector, and performs the following steps:</p>
<ol class="arabic simple">
<li><p>Extracts all model data, such as objective function and constraint right-hand sides into numpy arrays, which can later be easily and efficiently accessed without rebuilding the model or invoking the solver;</p></li>
@@ -366,9 +366,14 @@ x5 = (3, 2) 0.6803075671195984
<li><p>Solves the original mixed-integer optimization problem to optimality and stores its optimal solution, along with solve statistics, such as number of explored nodes and wallclock time.</p></li>
</ol>
<p>Data extracted in Phases 1, 2 and 3 above are prefixed, respectively as <code class="docutils literal notranslate"><span class="pre">static_</span></code>, <code class="docutils literal notranslate"><span class="pre">lp_</span></code> and <code class="docutils literal notranslate"><span class="pre">mip_</span></code>. The entire set of fields is shown in the table below.</p>
<section id="Data-fields">
<h3>Data fields<a class="headerlink" href="#Data-fields" title="Link to this heading"></a></h3>
<div class="section" id="Data-fields">
<h3>Data fields<a class="headerlink" href="#Data-fields" title="Permalink to this headline"></a></h3>
<table class="docutils align-default">
<colgroup>
<col style="width: 18%" />
<col style="width: 11%" />
<col style="width: 72%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Field</p></th>
<th class="head"><p>Type</p></th>
@@ -490,53 +495,53 @@ x5 = (3, 2) 0.6803075671195984
</tr>
</tbody>
</table>
</section>
<section id="id1">
<h3>Example<a class="headerlink" href="#id1" title="Link to this heading"></a></h3>
</div>
<div class="section" id="id1">
<h3>Example<a class="headerlink" href="#id1" title="Permalink to this headline"></a></h3>
<p>The example below shows how to generate a few random instances of the traveling salesman problem, store its problem data, run the collector and print some of the training data to screen.</p>
<div class="nbinput docutils container">
<div class="prompt highlight-none notranslate"><div class="highlight"><pre><span></span>[2]:
</pre></div>
</div>
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">random</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
<span class="kn">from</span> <span class="nn">scipy.stats</span> <span class="kn">import</span> <span class="n">uniform</span><span class="p">,</span> <span class="n">randint</span>
<span class="kn">from</span> <span class="nn">glob</span> <span class="kn">import</span> <span class="n">glob</span>
<div class="input_area highlight-ipython3 notranslate"><div class="highlight"><pre><span></span>import random
import numpy as np
from scipy.stats import uniform, randint
from glob import glob
<span class="kn">from</span> <span class="nn">miplearn.problems.tsp</span> <span class="kn">import</span> <span class="p">(</span>
<span class="n">TravelingSalesmanGenerator</span><span class="p">,</span>
<span class="n">build_tsp_model_gurobipy</span><span class="p">,</span>
<span class="p">)</span>
<span class="kn">from</span> <span class="nn">miplearn.io</span> <span class="kn">import</span> <span class="n">write_pkl_gz</span>
<span class="kn">from</span> <span class="nn">miplearn.h5</span> <span class="kn">import</span> <span class="n">H5File</span>
<span class="kn">from</span> <span class="nn">miplearn.collectors.basic</span> <span class="kn">import</span> <span class="n">BasicCollector</span>
from miplearn.problems.tsp import (
TravelingSalesmanGenerator,
build_tsp_model_gurobipy,
)
from miplearn.io import write_pkl_gz
from miplearn.h5 import H5File
from miplearn.collectors.basic import BasicCollector
<span class="c1"># Set random seed to make example reproducible.</span>
<span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
<span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
# Set random seed to make example reproducible.
random.seed(42)
np.random.seed(42)
<span class="c1"># Generate a few instances of the traveling salesman problem.</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">TravelingSalesmanGenerator</span><span class="p">(</span>
<span class="n">n</span><span class="o">=</span><span class="n">randint</span><span class="p">(</span><span class="n">low</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">high</span><span class="o">=</span><span class="mi">11</span><span class="p">),</span>
<span class="n">x</span><span class="o">=</span><span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">1000.0</span><span class="p">),</span>
<span class="n">y</span><span class="o">=</span><span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">1000.0</span><span class="p">),</span>
<span class="n">gamma</span><span class="o">=</span><span class="n">uniform</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="mf">0.90</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.20</span><span class="p">),</span>
<span class="n">fix_cities</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="nb">round</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="p">)</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
# Generate a few instances of the traveling salesman problem.
data = TravelingSalesmanGenerator(
n=randint(low=10, high=11),
x=uniform(loc=0.0, scale=1000.0),
y=uniform(loc=0.0, scale=1000.0),
gamma=uniform(loc=0.90, scale=0.20),
fix_cities=True,
round=True,
).generate(10)
<span class="c1"># Save instance data to data/tsp/00000.pkl.gz, data/tsp/00001.pkl.gz, ...</span>
<span class="n">write_pkl_gz</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="s2">&quot;data/tsp&quot;</span><span class="p">)</span>
# Save instance data to data/tsp/00000.pkl.gz, data/tsp/00001.pkl.gz, ...
write_pkl_gz(data, &quot;data/tsp&quot;)
<span class="c1"># Solve all instances and collect basic solution information.</span>
<span class="c1"># Process at most four instances in parallel.</span>
<span class="n">bc</span> <span class="o">=</span> <span class="n">BasicCollector</span><span class="p">()</span>
<span class="n">bc</span><span class="o">.</span><span class="n">collect</span><span class="p">(</span><span class="n">glob</span><span class="p">(</span><span class="s2">&quot;data/tsp/*.pkl.gz&quot;</span><span class="p">),</span> <span class="n">build_tsp_model_gurobipy</span><span class="p">,</span> <span class="n">n_jobs</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span>
# Solve all instances and collect basic solution information.
# Process at most four instances in parallel.
bc = BasicCollector()
bc.collect(glob(&quot;data/tsp/*.pkl.gz&quot;), build_tsp_model_gurobipy, n_jobs=4)
<span class="c1"># Read and print some training data for the first instance.</span>
<span class="k">with</span> <span class="n">H5File</span><span class="p">(</span><span class="s2">&quot;data/tsp/00000.h5&quot;</span><span class="p">,</span> <span class="s2">&quot;r&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">h5</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;lp_obj_value = &quot;</span><span class="p">,</span> <span class="n">h5</span><span class="o">.</span><span class="n">get_scalar</span><span class="p">(</span><span class="s2">&quot;lp_obj_value&quot;</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;mip_obj_value = &quot;</span><span class="p">,</span> <span class="n">h5</span><span class="o">.</span><span class="n">get_scalar</span><span class="p">(</span><span class="s2">&quot;mip_obj_value&quot;</span><span class="p">))</span>
# Read and print some training data for the first instance.
with H5File(&quot;data/tsp/00000.h5&quot;, &quot;r&quot;) as h5:
print(&quot;lp_obj_value = &quot;, h5.get_scalar(&quot;lp_obj_value&quot;))
print(&quot;mip_obj_value = &quot;, h5.get_scalar(&quot;mip_obj_value&quot;))
</pre></div>
</div>
</div>
@@ -557,9 +562,9 @@ mip_obj_value = 2921.0
</pre></div>
</div>
</div>
</section>
</section>
</section>
</div>
</div>
</div>
</div>