Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added some documentation to inits and added isort to automatic code generation. #23

Merged
merged 5 commits into from
Jun 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .zenodo.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"environmental-modelling"
],
"license": "GPL-2.0",
"title":"Environmental Science for SageMath",
"title":"Environmental Science using Symbolic Math",
"creators":[
{
"orcid":"0000-0002-0950-2942",
Expand Down
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
==================================
Environmental Science for SageMath
==================================
=========================================
Environmental Science using Symbolic Math
=========================================

.. image:: https://zenodo.org/badge/90965956.svg
:target: https://zenodo.org/badge/latestdoi/90965956
Expand Down
13 changes: 9 additions & 4 deletions essm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,25 @@
# along with essm; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
"""ESSM contains helpers to deal with physical variables and units.
"""Variables module to deal with physical variables and units.

It allows attaching docstrings to variable names, defining their domains
(e.g. integer, real or complex), their units and LaTeX representations.
You can also provide a default value, which is particularly useful for
physical constants.

Creating variables
==================
To create custom variables, first import `Variable`:

>>> from essm.variables import Variable

To define units, you can either import these units from the library:
To define units, you must first import these units from the library:

>>> from essm.variables.units import joule, kelvin, meter

Then you can define a custom variable with its name, description, domain,
latex_name, and unit, e.g.:
latex_name, unit, and an optional default value, e.g.:

>>> class demo_chamber_volume(Variable):
... '''Volume of chamber.'''
Expand Down Expand Up @@ -60,7 +65,7 @@
Creating equations
==================

To create custom equations, proceed similarly to avove, i.e. first import
To create custom equations, proceed similarly to above, i.e. first import
`Equation`:

>>> from essm.equations import Equation
Expand Down
14 changes: 11 additions & 3 deletions essm/_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import pkg_resources
from yapf.yapflib.yapf_api import FormatCode

from isort import SortImports
from sage import all as sage_all

from .variables import Variable
Expand Down Expand Up @@ -83,6 +84,13 @@ class {name}(Variable):
"""Regular expression to find sage-specific constants and functions."""


def _lint_content(content):
"""Automatically lint the generated code."""
content = SortImports(file_contents=content).output
content = FormatCode(content, style_config=STYLE_YAPF)[0]
return content


def create_module(name, doc=None, folder=None, overwrite=False):
"""Create folder with init file."""
name_path = (name.replace('.', os.path.sep), ) \
Expand Down Expand Up @@ -155,7 +163,7 @@ def __str__(self):
result += '\n\n__all__ = (\n{0}\n)'.format(
'\n'.join(
" '{0}',".format(var['name']) for var in self.vars))
result = FormatCode(result, style_config=STYLE_YAPF)[0]
result = _lint_content(result)
return result

def var(
Expand Down Expand Up @@ -260,8 +268,8 @@ def __str__(self):
self.TPL.format(**eq).replace('^', '**') for eq in self.eqs)
result += '\n\n__all__ = (\n{0}\n)'.format(
'\n'.join(" '{0}',".format(eq['name']) for eq in self.eqs))
reformatted_result = FormatCode(result, style_config=STYLE_YAPF)
return reformatted_result[0]
reformatted_result = _lint_content(result)
return reformatted_result

def eq(self, name, expr, doc='', parents=None, variables=None):
"""Add new equation."""
Expand Down
20 changes: 19 additions & 1 deletion essm/equations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,25 @@
# along with essm; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
"""Define equations."""
"""Equations module to deal with physical equations.

It allows attaching docstrings to equation names (including references)
and defining internal variables.

Creating equations
==================

To create custom equations, first import `Equation` and variables needed:

>>> from essm.equations import Equation
>>> from essm.variables.physics.thermodynamics import *

Importing pre-defined equations
=================================
You can import pre-defined equations as e.g.:

>>> from essm.equations.physics.thermodynamics import *
"""

from __future__ import absolute_import

Expand Down
46 changes: 25 additions & 21 deletions essm/equations/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# along with essm; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
"""Core equation type."""
"""Core equation type. Contains class definitions related to equations."""

from __future__ import absolute_import

Expand All @@ -40,49 +40,53 @@ class EquationMeta(type):

.. code-block:: python

from ..variables.units import meter, second
from ..variables.units import meter, second
class test(Equation):
'''Test equation.'''

class test(Equation):
\"\"\"Test equation.\"\"\"
class d(Variable):
'''Internal variable.'''
unit = meter

class d(Variable):
\"\"\"Test variable.\"\"\"
unit = meter
class t(Variable):
'''Internal variable.'''
unit = second

class t(Variable):
\"\"\"Test variable.\"\"\"
unit = second
class v(Variable):
'''Internal variable.'''
unit = meter/second

class v(Variable):
\"\"\"Test variable.\"\"\"
unit = meter/second

expr = v == d / t
expr = v == d / t

:raises ValueError: if the units are inconsistent.

Example:

.. code-block:: python
.. testcode:: python

from ..variables.units import meter, second

class test(Equation):
\"\"\"Test equation with inconsistent units.\"\"\"
'''Test equation with inconsistent units.'''

class d(Variable):
\"\"\"Test variable.\"\"\"
'''Internal variable.'''
unit = meter

class t(Variable):
\"\"\"Test variable.\"\"\"
'''Internal variable.'''
unit = second

class v(Variable):
\"\"\"Test variable.\"\"\"
'''Internal variable.'''
unit = meter/second

expr = v == d * t

Since the units of v and d*t are not the same, this returns:

.. testoutput::

ValueError: Invalid expression units: meter/second == meter*second
"""

def __new__(cls, name, parents, dct):
Expand Down
53 changes: 52 additions & 1 deletion essm/variables/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,58 @@
# along with essm; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
"""Define useful variables and symbols."""
"""Variables module to deal with physical variables and units.

It allows attaching docstrings to variable names, defining their domains
(e.g. integer, real or complex), their units and LaTeX representations.
You can also provide a default value, which is particularly useful for
physical constants.

Creating variables
==================
To create custom variables, first import `Variable`:

>>> from essm.variables import Variable

To define units, you must first import these units from the library:

>>> from essm.variables.units import joule, kelvin, meter

Then you can define a custom variable with its name, description, domain,
latex_name, unit, and an optional default value.

Example:

.. code-block:: python

from .variables.units import meter

class demo_chamber_volume1(Variable):
'''Volume of Chamber 1.'''

name = 'V_c1'
domain = 'real'
latex_name = 'V_{c1}'
unit = meter ** 3
default = 1

Now, demo_chamber_volume is displayed as V_c1 and it will be
nicely rendered in LaTeX as :math:`V_{c1}`.

You can type `help(demo_chamber_volume)` to inspects its metadata.

`Variable.__defaults__` returns a dictionary with all variables and
their default values, `Variable.__units__` returns their units, and
`demo_chamber_volume.short_unit()` can be used to obtain the units
in short notation.

This module also contains libraries of pre-defined variables, which
can be imported into your SageMath session, e.g.:

>>> from essm.variables.physics.thermodynamics import *
>>> from essm.variables.leaf.energy_water import *

"""

from __future__ import absolute_import

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# along with essm; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
"""Environmental Science for SageMath."""
"""Environmental Science using Symbolic Math."""

import os

Expand Down
56 changes: 46 additions & 10 deletions tests/test_equations.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,15 @@ def test_equation():
assert demo_fall.__doc__ == demo_fall.definition.__doc__
assert solve(
demo_fall.subs(Variable.__defaults__).subs(
demo_fall.definition.t == 1), demo_fall.definition.d) == [
demo_fall.definition.d == 4.9]
demo_fall.definition.t == 1),
demo_fall.definition.d) == [demo_fall.definition.d == 4.9]


def test_units():
"""Check units during definition."""
with pytest.raises(ValueError):

class invalid_units(Equation):

class x(Variable):
unit = meter

Expand All @@ -55,14 +54,14 @@ def test_args():
assert set(demo_fall.definition.args()) == {
demo_g.definition,
demo_fall.definition.d.definition,
demo_fall.definition.t.definition, }
demo_fall.definition.t.definition,
}


def test_unit_check():
"""Check unit test involving temperature."""

class combined_units(Equation):

class x_mol(Variable):
unit = joule / mole / kelvin

Expand All @@ -80,6 +79,7 @@ class x_M(Variable):

def test_double_registration():
"""Check double registration warning."""

class demo_double(Equation):
"""First."""

Expand All @@ -88,6 +88,7 @@ class demo_double(Equation):
assert Equation.__registry__[demo_double].__doc__ == 'First.'

with pytest.warns(UserWarning):

class demo_double(Equation): # ignore: W0232
"""Second."""

Expand All @@ -104,17 +105,52 @@ def test_equation_writer(tmpdir):
writer_td = EquationWriter(docstring='Test of Equation_writer.')
writer_td.eq(
'demo_fall',
demo_g == d / t ** 2,
demo_g == d / t**2,
doc='Test equation.\n\n (Some reference)\n ',
variables=[{
"name": "d",
"value": '0.9',
"units": meter,
"latexname": 'p_1'}, {
"name": "t",
"units": second,
"latexname": 'p_2'}])
"latexname": 'p_1'
}, {
"name": "t",
"units": second,
"latexname": 'p_2'
}])
eq_file = tmpdir.mkdir('test').join('test_equations.py')
writer_td.write(eq_file.strpath)
execfile(eq_file.strpath, g)
assert g['demo_fall'].definition.d.definition.default == 0.9


def test_equation_writer_linebreaks(tmpdir):
"""EquationWriter breaks long import lines."""
contents = {}
from essm.variables.physics.thermodynamics import *
writer_td = EquationWriter(docstring='Test of Equation_writer.')
writer_td.eq(
'eq_Le',
Le == alpha_a / D_va,
doc='Le as function of alpha_a and D_va.')
writer_td.eq(
'eq_Cwa',
C_wa == P_wa / (R_mol * T_a),
doc='C_wa as a function of P_wa and T_a.')
writer_td.eq(
'eq_rhoa_Pwa_Ta',
rho_a == (M_w * P_wa + M_N2 * P_N2 + M_O2 * P_O2) / (R_mol * T_a),
doc='rho_a as a function of P_wa and T_a.')
writer_td.eq(
'eq_Pa',
P_a == P_N2 + P_O2 + P_wa,
doc='Calculate air pressure from partial pressures.')
writer_td.eq(
'eq_PN2_PO2',
P_N2 == x_N2 / x_O2 * P_O2,
doc='Calculate P_N2 as a function of P_O2')

eq_file = tmpdir.mkdir('test').join('test_equations.py')
writer_td.write(eq_file.strpath)
with open(eq_file.strpath) as outfile:
maxlinelength = max([len(line) for line in outfile.readlines()])
assert maxlinelength < 80