diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..2b16911 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +source = mypyopt + +[report] +# this file generates a graph, not testing it during unit testing +omit = *mypyopt/demos/plot_example/main.py \ No newline at end of file diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml new file mode 100644 index 0000000..67c02f0 --- /dev/null +++ b/.github/workflows/flake8.yml @@ -0,0 +1,21 @@ +name: Flake8 + +on: [push] + +jobs: + flake8: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v2 + + - name: Set up Python 3.8 + uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5 # v4.2.0 + with: + python-version: 3.8 + + - name: Install Pip Dependencies + run: pip install flake8 + + - name: Run Flake8 + run: flake8 mypyopt diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml new file mode 100644 index 0000000..7bf50b0 --- /dev/null +++ b/.github/workflows/pypi.yml @@ -0,0 +1,32 @@ +name: Release_to_PyPi + +on: + push: + tags: + - '*' + +jobs: + release: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v2 + + - name: Set up Python + uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5 # v4.2.0 + with: + python-version: 3.8 + + - name: Install Pip Dependencies + shell: bash + run: pip install -r requirements.txt + + - name: Build the Wheel + shell: bash + run: rm -rf dist/ build/ && python3 setup.py bdist_wheel sdist + + - name: Deploy on Test PyPi + uses: pypa/gh-action-pypi-publish@37f50c210e3d2f9450da2cd423303d6a14a6e29f # v1.5.1 + with: + user: __token__ + password: ${{ secrets.PYPIPW }} diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 0000000..f1c1e60 --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,29 @@ +name: Run Tests + +on: [push] + +defaults: + run: + shell: bash + +jobs: + unit_tests: + strategy: + matrix: + os: [ windows-latest, macos-11, ubuntu-20.04, ubuntu-22.04 ] # macos-12 is not playing well with Tk + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v2 + - name: Set up Python 3.8 + uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5 # v4.2.0 + with: + python-version: 3.8 + - name: Install Pip Dependencies + run: pip install -r requirements.txt + - name: Run Tests + run: nosetests + - name: Coveralls + if: ${{ matrix.os == 'ubuntu-22.04' }} + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 02e70d2..6639df0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ __pycache__/ *.py[cod] *$py.class +projects +stop.stop # C extensions *.so diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 90af178..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: python - -env: - matrix: - - TOX_ENV=python - - TOX_ENV=flake8 - - TOX_ENV=spelling - -install: - - sudo apt-get install python-enchant -y - - pip install --upgrade pip - - pip install tox - -script: - - tox -e $TOX_ENV \ No newline at end of file diff --git a/demos/plot_example/requirements.txt b/demos/plot_example/requirements.txt deleted file mode 100644 index 4b43f7e..0000000 --- a/demos/plot_example/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -matplotlib \ No newline at end of file diff --git a/demos/pretend_energyplus/calibrate_walltemperatures.py b/demos/pretend_energyplus/calibrate_walltemperatures.py deleted file mode 100755 index d488025..0000000 --- a/demos/pretend_energyplus/calibrate_walltemperatures.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/python - -import sys -import os -import csv -import subprocess - -this_dir = os.path.dirname(os.path.realpath(__file__)) -sys.path.append(os.path.join(this_dir, '..', '..')) - -from mypyopt.ProjectStructure import ProjectStructure -from mypyopt.InputOutput import InputOutputManager -from mypyopt.DecisionVariable import DecisionVariable -from mypyopt.OptimizerHeuristicSearch import HeuristicSearch - - -# Actual "simulation" -def sim_pretend_energyplus(parameter_hash): - resistance_value = parameter_hash['wall_resistance'] - min_outdoor_temp = parameter_hash['min_outdoor_temp'] - template_contents = open(os.path.join(this_dir, 'in_template.json')).read() - new_contents = template_contents.replace('{wall_resistance}', - str(resistance_value)).replace('{min_outdoor_temp}', str(min_outdoor_temp)) - open(os.path.join(this_dir, 'in.json'), 'w').write(new_contents) - subprocess.call(['python', os.path.join(this_dir, 'pretend_energyplus.py'), - str(resistance_value)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) - f = open(os.path.join(this_dir, 'out.csv')) - reader = csv.reader(f) - surface_temps = list() - for row in reader: - surface_temps.append(float(row[1])) - return surface_temps - - -# Squared Error expression -def ssqe_pretend_energyplus(sim_values): - measured_temps = [22.790, 22.519, 22.789, 22.736, 22.948, 22.827, 22.988, 22.921, - 23.204, 23.211, 23.351, 23.678, 24.236, 24.062, 24.319, 24.535, - 24.735, 23.987, 23.947, 23.436, 23.465, 23.094, 22.904, 22.532] - sqe = [(a - b) ** 2 for a, b in zip(measured_temps, sim_values)] - return sum(sqe) - - -# Initialize list of decision variables -dvs = list() -dvs.append(DecisionVariable('wall_resistance', minimum=-10, maximum=10, initial_value=1, initial_step_size=1, - convergence_criterion=0.0001)) # opt value = 2 -dvs.append(DecisionVariable('min_outdoor_temp', minimum=-100, maximum=100, initial_value=10, initial_step_size=1, - convergence_criterion=0.0001)) # opt value = 20 - -# Initialize the IO manager -io = InputOutputManager() - -sim = ProjectStructure(expansion=1.2, contraction=0.85, max_iterations=2000, - project_name='RunPretendEnergyPlus', output_dir='projects', verbose=True) -searcher = HeuristicSearch(sim, dvs, sim_pretend_energyplus, ssqe_pretend_energyplus, io) -response = searcher.search() diff --git a/demos/wall_temperature/optimize_resistance.py b/demos/wall_temperature/optimize_resistance.py deleted file mode 100755 index e28368d..0000000 --- a/demos/wall_temperature/optimize_resistance.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/python - -import sys -import os -import subprocess - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..')) - -from mypyopt.ProjectStructure import ProjectStructure -from mypyopt.InputOutput import InputOutputManager -from mypyopt.DecisionVariable import DecisionVariable -from mypyopt.OptimizerHeuristicSearch import HeuristicSearch - - -# Actual "simulation" -def sim_wall_heat_transfer(parameter_hash): - resistance_value = parameter_hash['wall_resistance'] - p = subprocess.Popen(['python', os.path.join(os.path.dirname(os.path.realpath(__file__)), 'calculate_wall_temperature.py'), - str(resistance_value)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) - out, err = p.communicate() - return [float(str(out).strip())] - - -# Squared Error expression -def ssqe_wall_heat_flux(sim_values): - measured_heat_flux = 0.5 - simulated_heat_flux = sim_values[0] - sqe = (measured_heat_flux-simulated_heat_flux)**2 - return sqe - - -# Initialize list of decision variables -dvs = list() -dvs.append(DecisionVariable('wall_resistance', minimum=0, maximum=100, initial_value=10, initial_step_size=1, - convergence_criterion=0.001)) # opt value = 20 - -# Initialize the IO manager -io = InputOutputManager() - -sim = ProjectStructure(expansion=1.2, contraction=0.85, max_iterations=2000, project_name='CalibrateWallResistance', - output_dir='projects', verbose=True) -searcher = HeuristicSearch(sim, dvs, sim_wall_heat_transfer, ssqe_wall_heat_flux, io) -searcher.search() diff --git a/docs/Makefile b/docs/Makefile index 6d00efe..d4bb2cb 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,222 +1,20 @@ -# Makefile for Sphinx documentation +# Minimal makefile for Sphinx documentation # -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . BUILDDIR = _build -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help +# Put it first so that "make" without argument is like "make help". help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - -.PHONY: clean -clean: - rm -rf $(BUILDDIR)/* - -.PHONY: html -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -.PHONY: dirhtml -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -.PHONY: singlehtml -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -.PHONY: pickle -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -.PHONY: json -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -.PHONY: htmlhelp -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -.PHONY: qthelp -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/MyPyOpt.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/MyPyOpt.qhc" - -.PHONY: applehelp -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -.PHONY: devhelp -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/MyPyOpt" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/MyPyOpt" - @echo "# devhelp" - -.PHONY: epub -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -.PHONY: latex -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -.PHONY: latexpdf -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: latexpdfja -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: text -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -.PHONY: man -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -.PHONY: texinfo -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -.PHONY: info -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -.PHONY: gettext -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -.PHONY: changes -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -.PHONY: linkcheck -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -.PHONY: doctest -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -.PHONY: coverage -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -.PHONY: xml -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -.PHONY: pseudoxml -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." +.PHONY: help Makefile -.PHONY: spelling -spelling: - $(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling - @echo - @echo "Build finished. The spelling results are in $(BUILDDIR)/spelling." +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py index b87815f..30b1a69 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,299 +1,34 @@ -# -*- coding: utf-8 -*- +# Configuration file for the Sphinx documentation builder. # -# MyPyOpt documentation build configuration file, created by -# sphinx-quickstart on Mon Nov 28 13:13:50 2016. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('../mypyopt')) - -# -- General configuration ------------------------------------------------ - - -# Define some custom callbacks -def skip(app, what, name, obj, skip, options): - if name == "__init__": - return False - return skip +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +from pathlib import Path +import sys -def setup(app): - app.connect("autodoc-skip-member", skip) +project_root_path = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(project_root_path / 'mypyopt')) +project = 'MyPyOpt' +copyright = '2022, Edwin Lee' +author = 'Edwin Lee' +release = '0.1' -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinxcontrib.spelling', - 'sphinx.ext.mathjax' -] +extensions = ['sphinx.ext.autodoc',] -# Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'MyPyOpt' -copyright = u'2016, Edwin Lee' -author = u'Edwin Lee' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = u'0.1' -# The full version, including alpha/beta/rc tags. -release = u'0.1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'pyramid' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". +html_theme = 'alabaster' html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'MyPyOptdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'MyPyOpt.tex', u'MyPyOpt Documentation', - u'Edwin Lee', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'mypyopt', u'MyPyOpt Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'MyPyOpt', u'MyPyOpt Documentation', - author, 'MyPyOpt', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/docs/DecisionVariable.rst b/docs/decision_variable.rst similarity index 81% rename from docs/DecisionVariable.rst rename to docs/decision_variable.rst index ebcfe0b..26137c3 100644 --- a/docs/DecisionVariable.rst +++ b/docs/decision_variable.rst @@ -1,7 +1,7 @@ Decision Variable Class Documentation ===================================== -.. automodule:: DecisionVariable +.. automodule:: decision_variable :members: :undoc-members: :show-inheritance: diff --git a/docs/Exceptions.rst b/docs/exceptions.rst similarity index 83% rename from docs/Exceptions.rst rename to docs/exceptions.rst index 6299d02..85c2557 100644 --- a/docs/Exceptions.rst +++ b/docs/exceptions.rst @@ -1,7 +1,7 @@ Exceptions Class Documentation ============================== -.. automodule:: Exceptions +.. automodule:: exceptions :members: :undoc-members: :show-inheritance: diff --git a/docs/index.rst b/docs/index.rst index 063859e..d4b2797 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,15 +19,15 @@ Contents: .. toctree:: :maxdepth: 2 - DecisionVariable - Exceptions - InputOutput - ObjectiveEvaluation - OptimizationStructure - Optimizer - OptimizerHeuristicSearch - ReturnStateEnum - SearchReturnType + decision_variable + exceptions + input_output + objective_evaluation + optimization_structure + optimizer + optimizer_heuristic_search + return_state_enum + search_return_type Index and tables ================ diff --git a/docs/InputOutput.rst b/docs/input_output.rst similarity index 80% rename from docs/InputOutput.rst rename to docs/input_output.rst index 66bff50..557c75c 100644 --- a/docs/InputOutput.rst +++ b/docs/input_output.rst @@ -1,7 +1,7 @@ IO Class Documentation ====================== -.. automodule:: InputOutput +.. automodule:: input_output :members: :undoc-members: :show-inheritance: diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..32bb245 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/ObjectiveEvaluation.rst b/docs/objective_evaluation.rst similarity index 81% rename from docs/ObjectiveEvaluation.rst rename to docs/objective_evaluation.rst index 45dd42f..7b12d74 100644 --- a/docs/ObjectiveEvaluation.rst +++ b/docs/objective_evaluation.rst @@ -1,7 +1,7 @@ Objective Evaluation Result Class Documentation =============================================== -.. automodule:: ObjectiveEvaluation +.. automodule:: objective_evaluation :members: :undoc-members: :show-inheritance: diff --git a/docs/OptimizationStructure.rst b/docs/optimization_structure.rst similarity index 83% rename from docs/OptimizationStructure.rst rename to docs/optimization_structure.rst index ec7f84f..aae969c 100644 --- a/docs/OptimizationStructure.rst +++ b/docs/optimization_structure.rst @@ -1,7 +1,7 @@ Optimization Project Structure Class Documentation ================================================== -.. automodule:: ProjectStructure +.. automodule:: project_structure :members: :undoc-members: :show-inheritance: diff --git a/docs/Optimizer.rst b/docs/optimizer.rst similarity index 84% rename from docs/Optimizer.rst rename to docs/optimizer.rst index f2affc0..f274278 100644 --- a/docs/Optimizer.rst +++ b/docs/optimizer.rst @@ -1,7 +1,7 @@ Optimizer Base Class Documentation ================================== -.. automodule:: Optimizer +.. automodule:: optimizer :members: :undoc-members: :show-inheritance: diff --git a/docs/OptimizerHeuristicSearch.rst b/docs/optimizer_heuristic_search.rst similarity index 79% rename from docs/OptimizerHeuristicSearch.rst rename to docs/optimizer_heuristic_search.rst index 7d64413..c3dcbb8 100644 --- a/docs/OptimizerHeuristicSearch.rst +++ b/docs/optimizer_heuristic_search.rst @@ -1,7 +1,7 @@ Optimizer (Heuristic Search) Class Documentation ================================================ -.. automodule:: OptimizerHeuristicSearch +.. automodule:: optimizer_heuristic_search :members: :undoc-members: :show-inheritance: diff --git a/docs/ReturnStateEnum.rst b/docs/return_state_enum.rst similarity index 82% rename from docs/ReturnStateEnum.rst rename to docs/return_state_enum.rst index 2a77488..b152a28 100644 --- a/docs/ReturnStateEnum.rst +++ b/docs/return_state_enum.rst @@ -1,7 +1,7 @@ Return State Enumeration Class Documentation ============================================ -.. automodule:: ReturnStateEnum +.. automodule:: return_state_enum :members: :undoc-members: :show-inheritance: diff --git a/docs/SearchReturnType.rst b/docs/search_return_type.rst similarity index 81% rename from docs/SearchReturnType.rst rename to docs/search_return_type.rst index 5c54126..fd95db2 100644 --- a/docs/SearchReturnType.rst +++ b/docs/search_return_type.rst @@ -1,7 +1,7 @@ Search Return Type Class Documentation ====================================== -.. automodule:: SearchReturnType +.. automodule:: search_return_type :members: :undoc-members: :show-inheritance: diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt deleted file mode 100644 index 8b13789..0000000 --- a/docs/spelling_wordlist.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/driver.py b/driver.py deleted file mode 100644 index 641b02a..0000000 --- a/driver.py +++ /dev/null @@ -1,40 +0,0 @@ -import sys -import os -import shutil -import subprocess -import unittest - -valid_args = ['test', 'clean_projects', 'usage', 'docs'] - - -def usage(): - print("Usage: call with one of the following arguments:") - for arg in valid_args: - print(" " + sys.argv[0] + " " + arg) - -if len(sys.argv) != 2: - print("Error: Must call with 1 command line argument!") - usage() - sys.exit(1) -elif sys.argv[1] == valid_args[0]: - tests = unittest.TestLoader().discover('test') - unittest.TextTestRunner().run(tests) - sys.exit(0) -elif sys.argv[1] == valid_args[1]: - folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), "projects") - if os.path.exists(folder): - print("I'll delete this folder: " + folder) - shutil.rmtree(folder) - else: - print("Folder doesn't exist, I'm done") - sys.exit(0) -elif sys.argv[1] == valid_args[2]: - usage() - sys.exit(0) -elif sys.argv[1] == valid_args[3]: - subprocess.call(['make', '-C', 'docs', 'html']) - sys.exit(0) -else: - print("Error: Invalid command line argument passed in!") - usage() - sys.exit(1) diff --git a/mypyopt/DecisionVariable.py b/mypyopt/decision_variable.py similarity index 86% rename from mypyopt/DecisionVariable.py rename to mypyopt/decision_variable.py index 8ba03b7..1bc15bb 100644 --- a/mypyopt/DecisionVariable.py +++ b/mypyopt/decision_variable.py @@ -1,12 +1,14 @@ -from Exceptions import MyPyOptException +from mypyopt.exceptions import MyPyOptException -class DecisionVariable(object): +class DecisionVariable: """ A structure for defining a single dimension in the optimization parameter space """ - def __init__(self, variable_name, minimum=-10000, maximum=10000, initial_value=1, initial_step_size=0.1, - convergence_criterion=0.001): + def __init__(self, + variable_name: str, minimum: float = -10000, maximum: float = 10000, + initial_value: float = 1, initial_step_size: float = 0.1, convergence_criterion: float = 0.001 + ): """ The constructor for this class, which does all initialization, at a minimum, the user should define the variable_name, the others can be left defaulted if desired @@ -31,7 +33,7 @@ def __init__(self, variable_name, minimum=-10000, maximum=10000, initial_value=1 self.x_new = initial_value self.delta_x = initial_step_size - def to_dictionary(self): + def to_dictionary(self) -> dict: """ Converts the meaningful parts of this decision variable into a dictionary for project summary reports diff --git a/demos/README.md b/mypyopt/demos/README.md similarity index 100% rename from demos/README.md rename to mypyopt/demos/README.md diff --git a/test/__init__.py b/mypyopt/demos/__init__.py similarity index 100% rename from test/__init__.py rename to mypyopt/demos/__init__.py diff --git a/demos/plot_example/README.md b/mypyopt/demos/plot_example/README.md similarity index 100% rename from demos/plot_example/README.md rename to mypyopt/demos/plot_example/README.md diff --git a/mypyopt/demos/plot_example/__init__.py b/mypyopt/demos/plot_example/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/demos/plot_example/main.py b/mypyopt/demos/plot_example/main.py similarity index 58% rename from demos/plot_example/main.py rename to mypyopt/demos/plot_example/main.py index e788a93..0a5b7b2 100644 --- a/demos/plot_example/main.py +++ b/mypyopt/demos/plot_example/main.py @@ -1,65 +1,58 @@ #!/usr/bin/env python -import sys + +from pathlib import Path import threading -import Tkinter +from tkinter import Tk, TOP, BOTH import collections -import os -import matplotlib -matplotlib.use('TkAgg') +from mypyopt.project_structure import ProjectStructure +from mypyopt.input_output import InputOutputManager +from mypyopt.decision_variable import DecisionVariable +from mypyopt.optimizer_heuristic_search import HeuristicSearch + from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure -this_dir = os.path.dirname(os.path.realpath(__file__)) -sys.path.append(os.path.join(this_dir, '..', '..')) - -from mypyopt.ProjectStructure import ProjectStructure -from mypyopt.InputOutput import InputOutputManager -from mypyopt.DecisionVariable import DecisionVariable -from mypyopt.OptimizerHeuristicSearch import HeuristicSearch - - # stuff for the plot max_length = 60 -xvar = collections.deque(maxlen=max_length) -yvar = collections.deque(maxlen=max_length) +x_var = collections.deque(maxlen=max_length) +y_var = collections.deque(maxlen=max_length) # callbacks for the optimization def progress(completed_iteration_number, latest_objective_function_value): - xvar.append(completed_iteration_number) - yvar.append(latest_objective_function_value) + x_var.append(completed_iteration_number) + y_var.append(latest_objective_function_value) # Actual "simulation" def sim_quadratic(parameter_hash): x_values = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5] - for i in range(0, 100000): - j = i**2 + # for i in range(0, 100000): + # j = i**2 return [parameter_hash['a'] + parameter_hash['b'] * x + parameter_hash['c'] * (x ** 2) for x in x_values] # Squared Error expression -def ssqe_quadratic(sim_values): +def sum_sq_err_quadratic(sim_values): x_values = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5] actual_values = [1 + 2 * x + 3 * (x ** 2) for x in x_values] - sqe = [(a - b) ** 2 for a, b in zip(actual_values, sim_values)] + sqe = [(q - r) ** 2 for q, r in zip(actual_values, sim_values)] return sum(sqe) -class MyApp(Tkinter.Tk): +class MyApp(Tk): def __init__(self, parent): - Tkinter.Tk.__init__(self, parent) + super().__init__() self.wm_title("Embedding in TK") self.parent = parent - fig = matplotlib.figure.Figure(figsize=(5, 4), dpi=100) + fig = Figure(figsize=(5, 4), dpi=100) fig_sub_plot = fig.add_subplot(111) self.line1, = fig_sub_plot.plot([0], [0], 'r-') - self.canvas = matplotlib.backends.backend_tkagg.FigureCanvasTkAgg(fig, master=self) - self.canvas.show() - self.canvas.get_tk_widget().pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1) - self.canvas._tkcanvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1) + self.canvas = FigureCanvasTkAgg(fig, master=self) + self.canvas.draw() + self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) self.resizable(True, False) self.update_plot() @@ -73,8 +66,9 @@ def __init__(self, parent): convergence_criterion=0.000001)) # opt value = 3 io = InputOutputManager() sim = ProjectStructure(expansion=1.2, contraction=0.85, max_iterations=2000, - project_name='TestProject', output_dir='projects') - searcher = HeuristicSearch(sim, dvs, sim_quadratic, ssqe_quadratic, io, progress) + project_name='TestProject', + output_dir_path=Path(__file__).resolve().parent.parent.parent / 'projects') + searcher = HeuristicSearch(sim, dvs, sim_quadratic, sum_sq_err_quadratic, io, progress) self.thread1 = threading.Thread(target=searcher.search) # I know...I know this is bad; thread1 is going to make callbacks that eventually # hit the main GUI thread. Tk isn't as simple as gtk and wx for transferring ownership to the @@ -82,16 +76,19 @@ def __init__(self, parent): self.thread1.start() def update_plot(self): - if list(xvar) and list(yvar): - self.line1.set_data(list(xvar), list(yvar)) + if list(x_var) and list(y_var): + self.line1.set_data(list(x_var), list(y_var)) ax = self.canvas.figure.axes[0] - ax.set_xlim(min(list(xvar)), max(list(xvar))) - ax.set_ylim(min(list(yvar)), max(list(yvar))) + ax.set_xlim(min(list(x_var)), max(list(x_var))) + ax.set_ylim(min(list(y_var)), max(list(y_var))) self.canvas.draw() self.after(10, self.update_plot) -a = MyApp(None) -Tkinter.mainloop() +def run(): + a = MyApp(None) + a.mainloop() +if __name__ == "__main__": + run() diff --git a/demos/pretend_energyplus/README.md b/mypyopt/demos/pretend_energyplus/README.md similarity index 100% rename from demos/pretend_energyplus/README.md rename to mypyopt/demos/pretend_energyplus/README.md diff --git a/mypyopt/demos/pretend_energyplus/__init__.py b/mypyopt/demos/pretend_energyplus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mypyopt/demos/pretend_energyplus/calibrate_walltemperatures.py b/mypyopt/demos/pretend_energyplus/calibrate_walltemperatures.py new file mode 100755 index 0000000..f3dc13d --- /dev/null +++ b/mypyopt/demos/pretend_energyplus/calibrate_walltemperatures.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +import os +import csv +from pathlib import Path +import subprocess + +from mypyopt.project_structure import ProjectStructure +from mypyopt.input_output import InputOutputManager +from mypyopt.decision_variable import DecisionVariable +from mypyopt.optimizer_heuristic_search import HeuristicSearch + +this_dir = Path(__file__).resolve().parent + + +# Actual "simulation" +def sim_pretend_energyplus(parameter_hash): + resistance_value = parameter_hash['wall_resistance'] + min_outdoor_temp = parameter_hash['min_outdoor_temp'] + template_contents = (this_dir / 'in_template.json').read_text() + new_contents = template_contents.replace('{wall_resistance}', + str(resistance_value)).replace('{min_outdoor_temp}', str(min_outdoor_temp)) + (this_dir / 'in.json').write_text(new_contents) + subprocess.call( + ['python', str(this_dir / 'pretend_energyplus.py'), str(resistance_value)], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE + ) + surface_temps = list() + with open(os.path.join(this_dir, 'out.csv')) as f: + reader = csv.reader(f) + for row in reader: + surface_temps.append(float(row[1])) + return surface_temps + + +# Squared Error expression +def sum_sq_err_pretend_energyplus(sim_values): + measured_temps = [22.790, 22.519, 22.789, 22.736, 22.948, 22.827, 22.988, 22.921, + 23.204, 23.211, 23.351, 23.678, 24.236, 24.062, 24.319, 24.535, + 24.735, 23.987, 23.947, 23.436, 23.465, 23.094, 22.904, 22.532] + sqe = [(a - b) ** 2 for a, b in zip(measured_temps, sim_values)] + return sum(sqe) + + +def run(): + # Initialize list of decision variables + dvs = list() + dvs.append(DecisionVariable('wall_resistance', minimum=-10, maximum=10, initial_value=1, initial_step_size=1, + convergence_criterion=0.0001)) # opt value = 2 + dvs.append(DecisionVariable('min_outdoor_temp', minimum=-100, maximum=100, initial_value=10, initial_step_size=1, + convergence_criterion=0.0001)) # opt value = 20 + + # Initialize the IO manager + io = InputOutputManager() + + sim = ProjectStructure(expansion=1.2, contraction=0.85, max_iterations=2000, + project_name='RunPretendEnergyPlus', + output_dir_path=Path(__file__).resolve().parent.parent.parent / 'projects', verbose=True + ) + searcher = HeuristicSearch(sim, dvs, sim_pretend_energyplus, sum_sq_err_pretend_energyplus, io) + searcher.search() + + +if __name__ == "__main__": # pragma: no cover + run() diff --git a/demos/pretend_energyplus/in_template.json b/mypyopt/demos/pretend_energyplus/in_template.json similarity index 100% rename from demos/pretend_energyplus/in_template.json rename to mypyopt/demos/pretend_energyplus/in_template.json diff --git a/demos/pretend_energyplus/pretend_energyplus.py b/mypyopt/demos/pretend_energyplus/pretend_energyplus.py similarity index 100% rename from demos/pretend_energyplus/pretend_energyplus.py rename to mypyopt/demos/pretend_energyplus/pretend_energyplus.py diff --git a/demos/wall_temperature/README.md b/mypyopt/demos/wall_temperature/README.md similarity index 100% rename from demos/wall_temperature/README.md rename to mypyopt/demos/wall_temperature/README.md diff --git a/mypyopt/demos/wall_temperature/__init__.py b/mypyopt/demos/wall_temperature/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/demos/wall_temperature/calculate_wall_temperature.py b/mypyopt/demos/wall_temperature/calculate_wall_temperature.py similarity index 89% rename from demos/wall_temperature/calculate_wall_temperature.py rename to mypyopt/demos/wall_temperature/calculate_wall_temperature.py index a226d69..f1a65d3 100755 --- a/demos/wall_temperature/calculate_wall_temperature.py +++ b/mypyopt/demos/wall_temperature/calculate_wall_temperature.py @@ -7,4 +7,4 @@ resistance_value = float(sys.argv[1]) q_calculated = (outdoor_temp - indoor_temp)/resistance_value -print(q_calculated) \ No newline at end of file +print(q_calculated) diff --git a/mypyopt/demos/wall_temperature/optimize_resistance.py b/mypyopt/demos/wall_temperature/optimize_resistance.py new file mode 100755 index 0000000..1198c48 --- /dev/null +++ b/mypyopt/demos/wall_temperature/optimize_resistance.py @@ -0,0 +1,48 @@ +#!/usr/bin/python + +from pathlib import Path +import os +import subprocess + +from mypyopt.project_structure import ProjectStructure +from mypyopt.input_output import InputOutputManager +from mypyopt.decision_variable import DecisionVariable +from mypyopt.optimizer_heuristic_search import HeuristicSearch + + +# Actual "simulation" +def sim_wall_heat_transfer(parameter_hash): + resistance_value = parameter_hash['wall_resistance'] + p = subprocess.Popen(['python', os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'calculate_wall_temperature.py'), + str(resistance_value)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) + out_bytes, _ = p.communicate() + out_string = out_bytes.decode('utf-8').strip() + return [float(out_string)] + + +# Squared Error expression +def sum_sq_err_wall_heat_flux(sim_values): + measured_heat_flux = 0.5 + simulated_heat_flux = sim_values[0] + sqe = (measured_heat_flux-simulated_heat_flux)**2 + return sqe + + +def run(): + # Initialize list of decision variables + dvs = list() + dvs.append(DecisionVariable('wall_resistance', minimum=0, maximum=100, initial_value=10, initial_step_size=1, + convergence_criterion=0.001)) # opt value = 20 + + # Initialize the IO manager + io = InputOutputManager() + + sim = ProjectStructure(expansion=1.2, contraction=0.85, max_iterations=2000, project_name='CalibrateWallResistance', + output_dir_path=Path(__file__).resolve().parent.parent.parent / 'projects', verbose=True) + searcher = HeuristicSearch(sim, dvs, sim_wall_heat_transfer, sum_sq_err_wall_heat_flux, io) + searcher.search() + + +if __name__ == "__main__": # pragma: no cover + run() diff --git a/mypyopt/Exceptions.py b/mypyopt/exceptions.py similarity index 100% rename from mypyopt/Exceptions.py rename to mypyopt/exceptions.py diff --git a/mypyopt/InputOutput.py b/mypyopt/input_output.py similarity index 66% rename from mypyopt/InputOutput.py rename to mypyopt/input_output.py index dec094d..b61ba93 100644 --- a/mypyopt/InputOutput.py +++ b/mypyopt/input_output.py @@ -1,23 +1,24 @@ import sys +from typing import Optional, TextIO -class InputOutputManager(object): +class InputOutputManager: """ This class defines some input/output-related conveniences """ def __init__(self): """ - The constructor for this class. Currently it only defines the name of the stop file trigger (stop.stop) + The constructor for this class. Currently, it only defines the name of the stop file trigger (stop.stop) """ self.stopFile = 'stop.stop' @staticmethod - def write_line(console, full_output, string): + def write_line(console: bool, full_output: Optional[TextIO], string: str): """ A static method in the class used for convenience when printing out information. :param console: A boolean for whether to report the string to standard output - :param full_output: A file stream, which if not None, will be written to with the string + :param full_output: A file stream; if not None, it will be written to with the string :param string: The string to report; a newline is appended to the end if it doesn't have one already """ if console: diff --git a/mypyopt/ObjectiveEvaluation.py b/mypyopt/objective_evaluation.py similarity index 71% rename from mypyopt/ObjectiveEvaluation.py rename to mypyopt/objective_evaluation.py index c176263..5d56773 100644 --- a/mypyopt/ObjectiveEvaluation.py +++ b/mypyopt/objective_evaluation.py @@ -1,16 +1,19 @@ -class ObjectiveEvaluation(object): +from typing import Any + + +class ObjectiveEvaluation: """ This class defines the return type from an evaluation of the objective function. The objective function is generally intended to be minimized by the optimizer search() function, - so it often is a sum of squares error between some known quantity and the current outputs + so it is often a sum of squares error between some known quantity and the current outputs """ - def __init__(self, state, value, message=''): + def __init__(self, state: int, value: Any, message: str = ''): """ The constructor for the class :param state: One of the ReturnStateEnum enumerated constants - :param value: A single value representing the current objective function evaluation, often SSQE + :param value: A single value representing the current objective function evaluation, often sum-squared-error :param message: An optional message to add additional info for the user """ self.return_state = state diff --git a/mypyopt/Optimizer.py b/mypyopt/optimizer.py similarity index 64% rename from mypyopt/Optimizer.py rename to mypyopt/optimizer.py index 76af850..fed7583 100644 --- a/mypyopt/Optimizer.py +++ b/mypyopt/optimizer.py @@ -1,13 +1,26 @@ -from Exceptions import MyPyOptException +from abc import abstractmethod +from typing import Callable, Any, Dict, List, Optional +from mypyopt.decision_variable import DecisionVariable +from mypyopt.exceptions import MyPyOptException +from mypyopt.input_output import InputOutputManager +from mypyopt.project_structure import ProjectStructure +from mypyopt.search_return_type import SearchReturnType -class Optimizer(object): + +class Optimizer: """ This is a base class of an Optimizer to define the interface """ - def __init__(self, project_settings, decision_variable_array, callback_f_of_x, - callback_objective, input_output_worker=None, callback_progress=None, callback_completed=None): + def __init__( + self, project_settings: ProjectStructure, decision_variable_array: List[DecisionVariable], + callback_f_of_x: Callable[[Dict[str, float]], Any], + callback_objective: Callable[[Any], List[float]], + input_output_worker: Optional[InputOutputManager] = None, + callback_progress: Optional[Callable[[int, float], None]] = None, + callback_completed: Optional[Callable[[SearchReturnType], None]] = None + ): """ The constructor for the class. @@ -20,7 +33,7 @@ def __init__(self, project_settings, decision_variable_array, callback_f_of_x, output values, or possibly a hash of values. :param callback_objective: A Python function that accepts a single argument. This argument is exactly what comes out of the simulation (f_of_x) function. - The user can choose to return an array, a hash, whatever. + The user can choose to return an array, a dict, whatever. simulation run, to be used in comparing to some baseline :param input_output_worker: An InputOutput instance to allow easy access to IO operations :param callback_progress: An optional callback function that gets called each iteration and passed in an @@ -28,9 +41,19 @@ def __init__(self, project_settings, decision_variable_array, callback_f_of_x, :param callback_completed: An optional callback function that gets called at the end of the optimization search, with a SearchReturnType instance as the only argument """ - pass + self.project = project_settings + self.dvs = decision_variable_array + if input_output_worker: + self.io = input_output_worker + else: + self.io = InputOutputManager() + self.callback_f_of_x = callback_f_of_x + self.callback_objective = callback_objective + self.callback_progress = callback_progress + self.callback_completed = callback_completed - def search(self): + @abstractmethod + def search(self) -> SearchReturnType: """ This is the main driver function for the optimization. It walks the parameter space finding a minimum objective function. @@ -40,10 +63,11 @@ def search(self): raise MyPyOptException( "Tried to use search() on the Optimizer base class; verify derived class overrides this method") - def f_of_x(self, parameter_hash): + @abstractmethod + def f_of_x(self, parameter_hash: Dict[str, float]): """ This function calls the "f_of_x" callback function, getting outputs for the current parameter space; - then passes those outputs into the objective function callback as an array, which usually returns the SSQE + then passes those outputs into the objective function callback as an array, which usually returns the sum-sq-err between known values and current outputs. :param parameter_hash: A dictionary of parameters with keys as the variable names, and current variable values diff --git a/mypyopt/OptimizerHeuristicSearch.py b/mypyopt/optimizer_heuristic_search.py similarity index 76% rename from mypyopt/OptimizerHeuristicSearch.py rename to mypyopt/optimizer_heuristic_search.py index ff53245..e246da5 100644 --- a/mypyopt/OptimizerHeuristicSearch.py +++ b/mypyopt/optimizer_heuristic_search.py @@ -2,14 +2,17 @@ import collections import os import time +from typing import Callable, Any, Dict, List, Optional import uuid -from Exceptions import MyPyOptException -from ObjectiveEvaluation import ObjectiveEvaluation -from Optimizer import Optimizer -from ReturnStateEnum import ReturnStateEnum -from SearchReturnType import SearchReturnType -from InputOutput import InputOutputManager +from mypyopt.decision_variable import DecisionVariable +from mypyopt.exceptions import MyPyOptException +from mypyopt.objective_evaluation import ObjectiveEvaluation +from mypyopt.optimizer import Optimizer +from mypyopt.return_state_enum import ReturnStateEnum +from mypyopt.search_return_type import SearchReturnType +from mypyopt.input_output import InputOutputManager +from mypyopt.project_structure import ProjectStructure class HeuristicSearch(Optimizer): @@ -29,42 +32,29 @@ class HeuristicSearch(Optimizer): iterations is reached. """ - def __init__(self, project_settings, decision_variable_array, callback_f_of_x, - callback_objective, input_output_worker=None, callback_progress=None, callback_completed=None): - - # not really needed, but avoids warnings - super(Optimizer, self).__init__() - - # store the settings - self.project = project_settings - self.dvs = decision_variable_array - if input_output_worker: - self.io = input_output_worker - else: - self.io = InputOutputManager() - - # store the callback functions, which may be "None" for the progress/completed callbacks - self.callback_f_of_x = callback_f_of_x - self.callback_objective = callback_objective - self.callback_progress = callback_progress - self.callback_completed = callback_completed + def __init__( + self, project_settings: ProjectStructure, decision_variable_array: List[DecisionVariable], + callback_f_of_x: Callable[[Dict[str, float]], Any], + callback_objective: Callable[[Any], List[float]], + input_output_worker: Optional[InputOutputManager] = None, + callback_progress: Optional[Callable[[int, float], None]] = None, + callback_completed: Optional[Callable[[SearchReturnType], None]] = None + ): + + super().__init__(project_settings, decision_variable_array, callback_f_of_x, callback_objective, + input_output_worker, callback_progress, callback_completed) # the root project name is created/validated by the sim constructor, set up the folder for this particular run timestamp = time.strftime('%Y-%m-%d-%H-%M-%S') dir_name = os.path.join(self.project.output_dir, timestamp + "_" + self.project.project_name + "_" + str(uuid.uuid4())[0:8]) - # so on Windows, as seen in issue #22, when the unit test would try to tell Windows to create a folder in /usr, - # it would fail badly, such that I couldn't raise the MyPyOptException at all. So this is a workaround. - if dir_name.startswith('/') and os.name == 'nt': - raise MyPyOptException("Attempted to create Linux folder path on Windows, aborting...") - try: os.mkdir(dir_name) - except OSError: + except OSError: # pragma: no cover -- not trying to catch this raise MyPyOptException("Couldn't create project folder, check permissions, aborting...") - # output optimization information so we don't have to look in the source + # output optimization information, so we don't have to look in the source project_info_file_name = os.path.join(dir_name, 'project_info.json') with open(project_info_file_name, 'w') as f: project_info = dict() @@ -75,10 +65,10 @@ def __init__(self, project_settings, decision_variable_array, callback_f_of_x, # remove any previous files and open clean versions of the log files self.full_output_file = open(os.path.join(dir_name, 'full_output.log'), 'w') - if os.path.exists(self.io.stopFile): + if os.path.exists(self.io.stopFile): # pragma: no cover -- stop file usage is possibly slated for failure try: os.remove(self.io.stopFile) - except OSError: + except OSError: # pragma: no cover -- not trying to catch this raise MyPyOptException("Found stop file, but couldn't remove it, check permissions, aborting...") # check the dv array for duplicate names, as this would be invalid @@ -87,7 +77,7 @@ def __init__(self, project_settings, decision_variable_array, callback_f_of_x, if duplicate_names: raise MyPyOptException("Found duplicated names within decision variables, give each a unique name.") - def search(self): + def search(self) -> SearchReturnType: """ This is the main driver function for the optimization. It walks the parameter space finding a minimum objective function. @@ -99,12 +89,13 @@ def search(self): base_values = {dv.var_name: dv.x_base for dv in self.dvs} obj_base = self.f_of_x(base_values) j_base = obj_base.value - if obj_base.return_state == ReturnStateEnum.UserAborted: + if obj_base.return_state == ReturnStateEnum.UserAborted: # pragma: no cover -- stop file may be deprecated self.io.write_line(True, self.full_output_file, 'User aborted simulation via stop signal file...') r = SearchReturnType(False, ReturnStateEnum.UserAborted) if self.callback_completed: self.callback_completed(r) + self.full_output_file.close() return r elif not obj_base.return_state == ReturnStateEnum.Successful: self.io.write_line(True, self.full_output_file, @@ -112,6 +103,7 @@ def search(self): r = SearchReturnType(False, ReturnStateEnum.InvalidInitialPoint) if self.callback_completed: self.callback_completed(r) + self.full_output_file.close() return r # begin iteration loop @@ -119,27 +111,31 @@ def search(self): self.io.write_line(self.project.verbose, self.full_output_file, 'iter = ' + str(iteration)) - if os.path.exists(self.io.stopFile): + if os.path.exists(self.io.stopFile): # pragma: no cover -- not covering stop file stuff self.io.write_line(True, self.full_output_file, 'Found stop signal file in run directory; stopping now...') r = SearchReturnType(False, ReturnStateEnum.UserAborted) if self.callback_completed: self.callback_completed(r) + self.full_output_file.close() return r # begin DV loop for dv in self.dvs: - # setup a new point + # set up a new point dv.x_new = dv.x_base + dv.delta_x new_values = {dv.var_name: dv.x_new for dv in self.dvs} - if dv.x_new > dv.value_maximum or dv.x_new < dv.value_minimum: + if dv.x_new > dv.value_maximum or dv.x_new < dv.value_minimum: # pragma: no cover + # arranging the unit test to cover this condition is too much for now + # if we wanted to do it, we could have the objective function be a generator that yields a bad value self.io.write_line(True, self.full_output_file, 'infeasible DV, name=' + dv.var_name) r = SearchReturnType(False, ReturnStateEnum.InfeasibleDV) if self.callback_completed: self.callback_completed(r) + self.full_output_file.close() return r # then evaluate the new point @@ -154,7 +150,8 @@ def search(self): w(self.project.verbose, self.full_output_file, 'x_new=' + str([x.x_new for x in self.dvs])) w(self.project.verbose, self.full_output_file, 'j_new=' + str(j_new)) - if obj_new.return_state == ReturnStateEnum.UnsuccessfulOther: + if obj_new.return_state == ReturnStateEnum.UnsuccessfulOther: # pragma: no cover + # not covering this either, this is kind of a dumping ground for unexpected errors to fail nicely self.io.write_line(True, self.full_output_file, 'Optimization ended unexpectedly, check all inputs and outputs') self.io.write_line(True, self.full_output_file, @@ -162,6 +159,7 @@ def search(self): r = SearchReturnType(False, ReturnStateEnum.UnsuccessfulOther) if self.callback_completed: self.callback_completed(r) + self.full_output_file.close() return r elif (not obj_new.return_state == ReturnStateEnum.Successful) or (j_new > j_base): dv.delta_x = -self.project.coefficient_contract * dv.delta_x @@ -187,15 +185,16 @@ def search(self): r = SearchReturnType(True, ReturnStateEnum.Successful, converged_values) if self.callback_completed: self.callback_completed(r) + self.full_output_file.close() return r if self.callback_progress: self.callback_progress(iteration, j_base) - def f_of_x(self, parameter_hash): + def f_of_x(self, parameter_hash: Dict[str, float]): """ This function calls the "f_of_x" callback function, getting outputs for the current parameter space; - then passes those outputs into the objective function callback as an array, which usually returns the SSQE + then passes those outputs into the objective function callback as an array, which usually returns the sum-sq-err between known values and current outputs. """ @@ -207,5 +206,5 @@ def f_of_x(self, parameter_hash): error_to_minimize = self.callback_objective(simulation_results) return ObjectiveEvaluation(ReturnStateEnum.Successful, error_to_minimize) else: - return ObjectiveEvaluation(ReturnStateEnum.InfeasibleObj, None, + return ObjectiveEvaluation(ReturnStateEnum.InfeasibleObj, -999999, 'Function f(x) failed, probably infeasible output') diff --git a/mypyopt/ProjectStructure.py b/mypyopt/project_structure.py similarity index 67% rename from mypyopt/ProjectStructure.py rename to mypyopt/project_structure.py index 94e521e..236265f 100644 --- a/mypyopt/ProjectStructure.py +++ b/mypyopt/project_structure.py @@ -1,14 +1,18 @@ +from pathlib import Path +from typing import Optional import os -from Exceptions import MyPyOptException +from mypyopt.exceptions import MyPyOptException -class ProjectStructure(object): +class ProjectStructure: """ This class defines high level project-wide settings """ - def __init__(self, expansion=1.2, contraction=0.85, max_iterations=2000, project_name='project_name', - output_dir='projects', verbose=False): + def __init__( + self, expansion: float = 1.2, contraction: float = 0.85, max_iterations: int = 2000, + project_name: str = 'project_name', output_dir_path: Optional[Path] = None, verbose: bool = False + ): """ Constructor for this class @@ -16,13 +20,17 @@ def __init__(self, expansion=1.2, contraction=0.85, max_iterations=2000, project :param contraction: The contraction coefficient for walking through the parameter space in a poor direction :param max_iterations: The maximum number of iterations to sweep the entire parameter space :param project_name: A descriptive name for this project - :param output_dir: The root output directory to use for writing output data + :param output_dir_path: The root output directory to use for writing output data as a pathlib.Path :param verbose: A boolean to decide whether to write a lot to the command line or not """ + if output_dir_path is None: + output_dir = Path(__file__).resolve().parent.parent / 'projects' + else: + output_dir = str(output_dir_path) if not os.path.exists(output_dir): try: os.makedirs(output_dir) - except os.error: + except os.error: # pragma: no cover -- not trying to catch this raise MyPyOptException("Couldn't create root folder, aborting...") if expansion <= 1.0: raise MyPyOptException("Expansion coefficient is less than or equal to 1 (={0}), must be greater than 1.") diff --git a/mypyopt/ReturnStateEnum.py b/mypyopt/return_state_enum.py similarity index 80% rename from mypyopt/ReturnStateEnum.py rename to mypyopt/return_state_enum.py index 3318d87..1974533 100644 --- a/mypyopt/ReturnStateEnum.py +++ b/mypyopt/return_state_enum.py @@ -1,3 +1,6 @@ +from typing import List + + class ReturnStateEnum(object): """ This class simply defines some constants for how functions return @@ -21,6 +24,17 @@ class ReturnStateEnum(object): UserAborted = -9 """Search was stopped because the user forced it to stop""" + @staticmethod + def all_enums() -> List[int]: + return [ + ReturnStateEnum.Successful, + ReturnStateEnum.InfeasibleDV, + ReturnStateEnum.InfeasibleObj, + ReturnStateEnum.UnsuccessfulOther, + ReturnStateEnum.InvalidInitialPoint, + ReturnStateEnum.UserAborted, + ] + @staticmethod def enum_to_string(enum): """ diff --git a/mypyopt/SearchReturnType.py b/mypyopt/search_return_type.py similarity index 100% rename from mypyopt/SearchReturnType.py rename to mypyopt/search_return_type.py diff --git a/mypyopt/tests/__init__.py b/mypyopt/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mypyopt/tests/test_demos.py b/mypyopt/tests/test_demos.py new file mode 100644 index 0000000..8e59916 --- /dev/null +++ b/mypyopt/tests/test_demos.py @@ -0,0 +1,12 @@ +from unittest import TestCase + +from mypyopt.demos.pretend_energyplus.calibrate_walltemperatures import run as run_pretend_ep +from mypyopt.demos.wall_temperature.optimize_resistance import run as run_wall_temp + + +class TestDemos(TestCase): + def test_pretend_energyplus(self): + run_pretend_ep() + + def test_wall_temp(self): + run_wall_temp() diff --git a/test/test_main.py b/mypyopt/tests/test_main.py similarity index 61% rename from test/test_main.py rename to mypyopt/tests/test_main.py index 4822e27..af362ce 100644 --- a/test/test_main.py +++ b/mypyopt/tests/test_main.py @@ -1,15 +1,14 @@ -import os -import sys +from pathlib import Path +from tempfile import mkdtemp import unittest -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')) - -from mypyopt.ProjectStructure import ProjectStructure -from mypyopt.InputOutput import InputOutputManager -from mypyopt.DecisionVariable import DecisionVariable -from mypyopt.OptimizerHeuristicSearch import HeuristicSearch -from mypyopt.Exceptions import MyPyOptException -from mypyopt.ReturnStateEnum import ReturnStateEnum +from mypyopt.project_structure import ProjectStructure +from mypyopt.input_output import InputOutputManager +from mypyopt.decision_variable import DecisionVariable +from mypyopt.optimizer import Optimizer +from mypyopt.optimizer_heuristic_search import HeuristicSearch +from mypyopt.exceptions import MyPyOptException +from mypyopt.return_state_enum import ReturnStateEnum class TestQuadratic(unittest.TestCase): @@ -33,7 +32,7 @@ def setUp(self): # Initialize a project structure self.sim = ProjectStructure(expansion=1.2, contraction=.85, max_iterations=2000, project_name='TestProject', - output_dir='projects') + output_dir_path=Path(__file__).resolve().parent.parent.parent / 'projects') # Actual "simulation" @staticmethod @@ -43,7 +42,7 @@ def sim_quadratic(parameter_hash): # Squared Error expression @staticmethod - def ssqe_quadratic(sim_values): + def sum_squared_error_quadratic(sim_values): x_values = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5] actual_values = [1 + 2 * x + 3 * (x ** 2) for x in x_values] sqe = [(a - b) ** 2 for a, b in zip(actual_values, sim_values)] @@ -63,7 +62,7 @@ def progress(completed_iteration_number, latest_objective_function_value): def test_quadratic1(self): searcher = HeuristicSearch(self.sim, self.dvs, self.sim_quadratic, - self.ssqe_quadratic, self.io, self.progress, self.completed) + self.sum_squared_error_quadratic, self.io, self.progress, self.completed) response = searcher.search() self.assertTrue(response.success) self.assertAlmostEqual(1.0, response.values['a'], 3) @@ -76,7 +75,7 @@ def test_quadratic_bad_folder(self): sim2.output_dir = '/usr' with self.assertRaises(MyPyOptException): HeuristicSearch(sim2, self.dvs, self.sim_quadratic, - self.ssqe_quadratic, self.io, self.progress, self.completed) + self.sum_squared_error_quadratic, self.io, self.progress, self.completed) def test_duplicated_dv_names(self): these_dvs = self.dvs @@ -85,7 +84,14 @@ def test_duplicated_dv_names(self): convergence_criterion=0.000001)) with self.assertRaises(MyPyOptException): HeuristicSearch(self.sim, these_dvs, self.sim_quadratic, - self.ssqe_quadratic, self.io, self.progress, self.completed) + self.sum_squared_error_quadratic, self.io, self.progress, self.completed) + + def test_heuristic_bad_init_value(self): + searcher = HeuristicSearch(self.sim, self.dvs, lambda _: None, self.sum_squared_error_quadratic, + callback_completed=lambda _: None) + response = searcher.search() + self.assertFalse(response.success) + self.assertEqual(ReturnStateEnum.InvalidInitialPoint, response.reason) class TestDefaults(unittest.TestCase): @@ -101,13 +107,13 @@ def sim_linear(parameter_hash): a, b = [parameter_hash[x] for x in ['a', 'b']] return [a + b * x for x in [0, 1, 2]] - def calc_ssqe(sim_values): + def calc_sum_squared_error(sim_values): actual = [1 + 2 * x for x in [0, 1, 2]] return [(a - b) ** 2 for a, b in zip(actual, sim_values)] dvs = [DecisionVariable('a'), DecisionVariable('b')] sim = ProjectStructure(verbose=True) - searcher = HeuristicSearch(sim, dvs, sim_linear, calc_ssqe) + searcher = HeuristicSearch(sim, dvs, sim_linear, calc_sum_squared_error) response = searcher.search() self.assertTrue(response.success) self.assertAlmostEqual(1.0, response.values['a'], 2) @@ -115,7 +121,7 @@ def calc_ssqe(sim_values): def test_minimal_minimal(self): """ - This test is an extremely minimal demonstration, where we are trying to solve for a, in the equation "a=4" + This test is an extremely minimal demonstration, where we are trying to solve for "a", in the equation "a=4" This test demonstrates completely default parameters, lambdas instead of explicit functions, and returning a single, scalar variable from the sim function instead of an array or other structure """ @@ -126,6 +132,47 @@ def test_minimal_minimal(self): self.assertTrue(response.success) self.assertAlmostEqual(4.0, response.values['a'], 3) -# allow execution directly as python tests/test_main.py -if __name__ == '__main__': - unittest.main() + +class TestDecisionVariables(unittest.TestCase): + def test_bad_inputs(self): + with self.assertRaises(MyPyOptException): + DecisionVariable('var_name', minimum=1.0, maximum=0.0) + with self.assertRaises(MyPyOptException): + DecisionVariable('var_name', initial_step_size=-1) + with self.assertRaises(MyPyOptException): + DecisionVariable('var_name', convergence_criterion=-1) + + +class TestBaseOptimizerAbstraction(unittest.TestCase): + def test_abstraction(self): + dvs = [DecisionVariable('a'), DecisionVariable('b')] + sim = ProjectStructure(verbose=True) + o = Optimizer(sim, dvs, lambda: None, lambda: [1]) + with self.assertRaises(MyPyOptException): + o.search() + with self.assertRaises(MyPyOptException): + o.f_of_x({}) + + +class TestReturnStateEnums(unittest.TestCase): + def test_all_enums(self): + all_enums = ReturnStateEnum.all_enums() + for e in all_enums: + self.assertIsInstance(ReturnStateEnum.enum_to_string(e), str) + + +class TestProjectStructureConstruction(unittest.TestCase): + def test_it_creates_output_folder(self): + temp_output_dir = Path(mkdtemp()) + temp_output_dir.rmdir() + self.assertFalse(temp_output_dir.exists()) + ProjectStructure(output_dir_path=temp_output_dir) + self.assertTrue(temp_output_dir.exists()) + + def test_bad_inputs(self): + with self.assertRaises(MyPyOptException): + ProjectStructure(expansion=0.5) + with self.assertRaises(MyPyOptException): + ProjectStructure(contraction=2.5) + with self.assertRaises(MyPyOptException): + ProjectStructure(max_iterations=0) diff --git a/requirements.txt b/requirements.txt index 9750374..34f7c89 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ coverage -tox -sphinxcontrib-spelling \ No newline at end of file +flake8 +nose +matplotlib # just for demo scripts \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..c8d4373 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,11 @@ +[nosetests] +traverse-namespace=1 +with-coverage=1 +cover-erase=1 +cover-html=1 +cover-package=mypyopt +cover-tests=1 +cover-inclusive=1 + +[flake8] +max-line-length = 120 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..74f9331 --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +from pathlib import Path +from setuptools import setup + + +readme_file = Path(__file__).parent.resolve() / 'README.md' +readme_contents = readme_file.read_text() + +setup( + name='my-py-opt', + version="0.2", + description='Lightweight optimization library', + url='https://github.com/Myoldmopar/MyPyOpt', + license='UnlicensedForNow', + packages=[ + 'mypyopt', 'mypyopt.demos.plot_example', 'mypyopt.demos.pretend_energyplus', 'mypyopt.demos.wall_temperature' + ], + package_data={ + 'mypyopt.demos.pretend_energyplus': ['in_template.json'], + }, + include_package_data=True, + long_description=readme_contents, + long_description_content_type='text/markdown', + author="Edwin Lee", + install_requires=[], +) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 639e7bd..0000000 --- a/tox.ini +++ /dev/null @@ -1,34 +0,0 @@ -[flake8] -ignore = E402,E501,E731,W503 -# exclude = migrations,management,docs,bin,seed/lib/superperms,seed/test_helpers/factory/*,test_helpers.py,local_untracked.py,venv -max-line-length = 100 - -[tox] -envlist = - python, - flake8, - spelling -skipsdist = True - -[testenv:python] -commands= - coverage run driver.py test -deps= - -r{toxinidir}/requirements.txt - -[testenv:flake8] -basepython = python -deps= - flake8 -commands=flake8 {toxinidir}/mypyopt - -[testenv:spelling] -basepython = python -changedir=docs -commands= - make spelling -deps= - -r{toxinidir}/requirements.txt -whitelist_externals= - make - cp