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

Improvements to pl-matrix-output element #1453

Merged
merged 30 commits into from Mar 31, 2019
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
89a673f
Closes #1365 and will add local digit control
eliving2 Mar 7, 2019
1761d22
Added local digit control
eliving2 Mar 7, 2019
a254d7e
Added to changelog and readme, fixed python lint
eliving2 Mar 7, 2019
3af704b
Requested changes to element and doc
lizliv Mar 12, 2019
488dca9
New example question outline started
lizliv Mar 12, 2019
94ca689
Merge branch 'master' into matrix-ouput-update
nwalters512 Mar 12, 2019
b0caa5a
Added matrix output boxes to example question
eliving2 Mar 12, 2019
f523389
Completed example question/Added tab-display attributes
lizliv Mar 12, 2019
6b04794
Style fix
lizliv Mar 12, 2019
a90e3a6
Added language='mathematica' to pl.string_from_2darray
eliving2 Mar 12, 2019
57b56ec
Update ChangeLod.md
eliving2 Mar 12, 2019
06829a5
Style fix
eliving2 Mar 12, 2019
ed47197
Requested changes implemented
eliving2 Mar 13, 2019
3e92f09
Fixed up example question
eliving2 Mar 13, 2019
9b286b8
More sensical tab control
eliving2 Mar 13, 2019
93a7818
Changed element to pl-variable-output
lizliv Mar 14, 2019
befa2fa
Small style/typo fixes
lizliv Mar 14, 2019
7b8ddca
Added 1D vector support for the element
eliving2 Mar 14, 2019
37c5633
Python lint...
eliving2 Mar 14, 2019
9e2bf10
Fixed selection of default tab
eliving2 Mar 14, 2019
9e9884e
Changed active tab control to also be an array
eliving2 Mar 14, 2019
4263152
Reverted to previous indentation
eliving2 Mar 14, 2019
c6ac880
Small fixes to pl-variable-output
eliving2 Mar 17, 2019
5e20a18
Changed `string_from_2darray` to `string_from_numpy`
eliving2 Mar 18, 2019
18b0f99
Fixed comment spacing and made variables in example unique
eliving2 Mar 18, 2019
fb9dd95
Merge branch 'master' into matrix-ouput-update
eliving2 Mar 30, 2019
99d5db2
Merged master into branch, fixed changelog spacing
eliving2 Mar 30, 2019
b4f694a
Added deprecated `string_from_2darray()`
eliving2 Mar 30, 2019
caa9f15
Added to/reworded changes in changelog
eliving2 Mar 30, 2019
846d035
Python lint
eliving2 Mar 30, 2019
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
10 changes: 10 additions & 0 deletions ChangeLog.md
Expand Up @@ -51,6 +51,14 @@

* Add student file storage for scratch paper scans (Matt West).

* Add Mathematica language option to `string_from_2darray` (Liz Livingston).

* Add Mathematica tab and optional display attribute to `pl-variable-output` (Liz Livingston).

* Add comment and child digit control for `pl-variable-output`, optional `comment` and `digits` (Liz Livingston).

* Add vector support to `numpy_to_matlab` and `numpy_to_matlab_sf` (Liz Livingston).

* Change "Save & Grade" button text and alignment (Dave Mussulman).

* Change Ace editor to use source files from npm and upgrade to 1.4.1 from 1.2.8 (Nathan Walters).
Expand Down Expand Up @@ -81,6 +89,8 @@

* Change `cheerio` back to `v0.22.0` (Nathan Walters).

* Change `pl-matrix-output` to `pl-variable-output` (Liz Livingston).

* Fix load-reporting close during unit tests (Matt West).

* Fix PL / scheduler linking stored procedure to allow linked exams and fix bugs (Dave Mussulman).
Expand Down
25 changes: 18 additions & 7 deletions doc/elements.md
Expand Up @@ -193,39 +193,50 @@ In the answer panel, a `pl-matrix-input` element displays the correct answer, al

In the submission panel, a `pl-matrix-input` element displays either the submitted answer (in the same format that it was submitted, either matlab or python), or a note that the submitted answer was invalid (with an explanation of why).

## `pl-matrix-output` element
## `pl-variable-output` element

```html
<pl-matrix-output digits="3">
<pl-variable-output digits="3">
<variable params-name="A">A</variable>
<variable params-name="B">B</variable>
</pl-matrix-output>
</pl-variable-output>
```

Attributes for `<pl-matrix-output`:
Attributes for `<pl-variable-output`:

Attribute | Type | Default | Description
--- | --- | --- | ---
`digits` | integer | — | Number of digits to display after the decimal.
`default-tab` | string | 'matlab' | Select the active tab.
`show-matlab` | boolean | True | Toggles the display of the Matlab tab.
`show-mathematica` | boolean | True | Toggles the display of the Mathematica tab.
`show-python` | boolean | True | Toggles the display of the Python tab.

Attributes for `<variable>` (one of these for each variable to display):

Attribute | Type | Default | Description
--- | --- | --- | ---
`params-name` | string | — | Name of variable in `data['params']` to display.
`comment` | string | — | Comment to add after the displayed variable.
`digits` | integer | — | Number of digits to display after the decimal for the variable.

This element displays a list of variables inside `<pre>` tags that are formatted for import into either MATLAB or python (the user can switch between the two). Each variable must be either a scalar or a 2D numpy array (expressed as a list). Each variable will be prefixed by the text that appears between the `<variable>` and `</variable>` tags, followed by ` = `.
This element displays a list of variables inside `<pre>` tags that are formatted for import into either MATLAB, Mathematica, or Python (the user can switch between them). Each variable must be either a scalar or a 2D numpy array (expressed as a list). Each variable will be prefixed by the text that appears between the `<variable>` and `</variable>` tags, followed by ` = `.

Here is an example of MATLAB format:
```
A = [1.23; 4.56];
A = [1.23; 4.56]; % matrix
```

Here is an example of the Mathematica format:
```
A = [1.23; 4.56]; (* matrix *)
```

Here is an example of python format:
```
import numpy as np

A = np.array([[1.23], [4.56]])
A = np.array([[1.23], [4.56]]) # matrix
```

If a variable `v` is a complex object, you should use `import prairielearn as pl` and `data['params'][params-name] = pl.to_json(v)`.
Expand Down
11 changes: 11 additions & 0 deletions elements/pl-variable-output/info.json
@@ -0,0 +1,11 @@
{
"controller": "pl-variable-output.py",
"dependencies": {
"nodeModulesScripts": [
"clipboard/dist/clipboard.min.js"
],
"coreScripts": [
"PrairieUtil.js"
]
}
}
39 changes: 39 additions & 0 deletions elements/pl-variable-output/pl-variable-output.mustache
@@ -0,0 +1,39 @@
<script>
$(function(){
PrairieUtil.initCopyButton('#pl-variable-output-{{uuid}} .copy-button');
});
</script>

<div id="pl-variable-output-{{uuid}}" class="card mb-4">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" role="tablist">
{{#show_matlab}}<li class="nav-item" role="presentation"><a class="nav-link{{#active_tab_matlab}} active{{/active_tab_matlab}}" href="#matlab-{{uuid}}" aria-controls="matlab-{{uuid}}" role="tab" data-toggle="pill">Matlab</a></li>{{/show_matlab}}

{{#show_mathematica}}<li class="nav-item" role="presentation"><a class="nav-link{{#active_tab_mathematica}} active{{/active_tab_mathematica}}" href="#mathematica-{{uuid}}" aria-controls="mathematica-{{uuid}}" role="tab" data-toggle="pill">Mathematica</a></li>{{/show_mathematica}}

{{#show_python}}<li class="nav-item" role="presentation"><a class="nav-link{{#active_tab_python}} active{{/active_tab_python}}" href="#python-{{uuid}}" aria-controls="python-{{uuid}}" role="tab" data-toggle="pill">Python</a></li>{{/show_python}}
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div role="tabpanel" class="tab-pane {{#active_tab_matlab}}active{{/active_tab_matlab}}" id="matlab-{{uuid}}">
<pre class="bg-dark text-white rounded p-2" id="matlab-data-{{uuid}}">{{matlab_data}}</pre>
<button type="button" class="btn btn-secondary btn-sm copy-button" data-clipboard-target="#matlab-data-{{uuid}}">
copy this text
</button>
</div>
<div role="tabpanel" class="tab-pane {{#active_tab_mathematica}}active{{/active_tab_mathematica}}" id="mathematica-{{uuid}}">
<pre class="bg-dark text-white rounded p-2" id="mathematica-data-{{uuid}}">{{mathematica_data}}</pre>
<button type="button" class="btn btn-secondary btn-sm copy-button" data-clipboard-target="#mathematica-data-{{uuid}}">
copy this text
</button>
</div>
<div role="tabpanel" class="tab-pane {{#active_tab_python}}active{{/active_tab_python}}" id="python-{{uuid}}">
<pre class="bg-dark text-white rounded p-2" id="python-data-{{uuid}}">{{python_data}}</pre>
<button type="button" class="btn btn-secondary btn-sm copy-button" data-clipboard-target="#python-data-{{uuid}}">
copy this text
</button>
</div>
</div>
</div>
</div>
127 changes: 127 additions & 0 deletions elements/pl-variable-output/pl-variable-output.py
@@ -0,0 +1,127 @@
import prairielearn as pl
import lxml.html
import numpy as np
import chevron


def prepare(element_html, data):
element = lxml.html.fragment_fromstring(element_html)
required_attribs = []
optional_attribs = ['digits', 'default-tab', 'show-matlab', 'show-mathematica', 'show-python']
pl.check_attribs(element, required_attribs, optional_attribs)


def render(element_html, data):
element = lxml.html.fragment_fromstring(element_html)
digits = pl.get_integer_attrib(element, 'digits', 2)
show_matlab = pl.get_boolean_attrib(element, 'show-matlab', True)
show_mathematica = pl.get_boolean_attrib(element, 'show-mathematica', True)
show_python = pl.get_boolean_attrib(element, 'show-python', True)
default_tab = pl.get_string_attrib(element, 'default-tab', 'matlab')

tab_list = ['matlab', 'mathematica', 'python']
if default_tab not in tab_list:
raise Exception(f'invalid default-tab: {default_tab}')

# Setting the default tab
displayed_tab = [show_matlab, show_mathematica, show_python]
eliving2 marked this conversation as resolved.
Show resolved Hide resolved
if not any(displayed_tab):
raise Exception('All tabs have been hidden from display. At least one tab must be shown.')

default_tab_index = tab_list.index(default_tab)
# If not displayed, make first visible tab the default
if not displayed_tab[default_tab_index]:
first_display = displayed_tab.index(True)
default_tab = tab_list[first_display]
default_tab_index = tab_list.index(default_tab)

# Active tab should be the default tab
default_tab_list = [False, False, False]
default_tab_list[default_tab_index] = True
[active_tab_matlab, active_tab_mathematica, active_tab_python] = default_tab_list
eliving2 marked this conversation as resolved.
Show resolved Hide resolved

# Process parameter data
matlab_data = ''
mathematica_data = ''
python_data = 'import numpy as np\n\n'
for child in element:
if child.tag == 'variable':
# Raise exception if variable does not have a name
pl.check_attribs(child, required_attribs=['params-name'], optional_attribs=['comment', 'digits'])

# Get name of variable
var_name = pl.get_string_attrib(child, 'params-name')

# Get value of variable, raising exception if variable does not exist
var_data = data['params'].get(var_name, None)
if var_data is None:
raise Exception('No value in data["params"] for variable %s in pl-variable-output element' % var_name)

# If the variable is in a format generated by pl.to_json, convert it
# back to a standard type (otherwise, do nothing)
var_data = pl.from_json(var_data)

# Get comment, if it exists
var_matlab_comment = ''
var_mathematica_comment = ''
var_python_comment = ''
if pl.has_attrib(child, 'comment'):
var_comment = pl.get_string_attrib(child, 'comment')
var_matlab_comment = f' % {var_comment}'
var_mathematica_comment = f' (* {var_comment} *)'
var_python_comment = f' # {var_comment}'

# Get digit for child, if it exists
if not pl.has_attrib(child, 'digits'):
var_digits = digits
else:
var_digits = pl.get_string_attrib(child, 'digits')

# Assembling Python array formatting
if np.isscalar(var_data):
prefix = ''
suffix = ''
else:
# Wrap the variable in an ndarray (if it's already one, this does nothing)
var_data = np.array(var_data)
# Check shape of variable
if var_data.ndim > 2:
raise Exception('Value in data["params"] for variable %s in pl-variable-output element must be a scalar, a vector, or a 2D array' % var_name)
# Create prefix/suffix so python string is np.array( ... )
prefix = 'np.array('
suffix = ')'

# Mathematica reserved letters: C D E I K N O
mathematica_reserved = ['C', 'D', 'E', 'I', 'K', 'N', 'O']
if pl.inner_html(child) in mathematica_reserved:
mathematica_suffix = 'm'
else:
mathematica_suffix = ''

# Create string for matlab and python format
var_name_disp = pl.inner_html(child)
var_matlab_data = pl.string_from_numpy(var_data, language='matlab', digits=var_digits)
var_mathematica = pl.string_from_numpy(var_data, language='mathematica', digits=var_digits)
var_python_data = pl.string_from_numpy(var_data, language='python', digits=var_digits)

matlab_data += f'{var_name_disp} = {var_matlab_data};{var_matlab_comment}\n'
mathematica_data += f'{var_name_disp}{mathematica_suffix} = {var_mathematica};{var_mathematica_comment}\n'
python_data += f'{var_name_disp} = {prefix}{var_python_data}{suffix}{var_python_comment}\n'

html_params = {
'active_tab_matlab': active_tab_matlab,
'active_tab_mathematica': active_tab_mathematica,
'active_tab_python': active_tab_python,
'show_matlab': show_matlab,
'show_mathematica': show_mathematica,
'show_python': show_python,
'matlab_data': matlab_data,
'mathematica_data': mathematica_data,
'python_data': python_data,
'uuid': pl.get_uuid()
}

with open('pl-variable-output.mustache', 'r', encoding='utf-8') as f:
html = chevron.render(f, html_params).strip()

return html
Expand Up @@ -38,7 +38,8 @@
{"id": "elementsMatrixLatexDisplay", "points": 2, "maxPoints": 10},
{"id": "examplesMatrixInput", "points": 2, "maxPoints": 10},
{"id": "examplesSymbolicInput", "points": 2, "maxPoints": 10},
{"id": "examplesNumberInput", "points": 2, "maxPoints": 10}
{"id": "examplesNumberInput", "points": 2, "maxPoints": 10},
{"id": "examplesVariableOutput", "points": 2, "maxPoints": 10}
]
}
]
Expand Down
1 change: 1 addition & 0 deletions exampleCourse/infoCourse.json
Expand Up @@ -26,6 +26,7 @@
{"name": "balamut2", "color": "orange1", "description": "Question originally written by balamut2"},
{"name": "mfsilva", "color": "orange1", "description": "Question originally written by mfsilva"},
{"name": "ressick2", "color": "gray1", "description": "Question originally written by ressick2"},
{"name": "eliving2", "color": "gray1", "description": "Question originally written by eliving2"},
{"name": "tpl101", "color": "gray1", "description": "Question originally written for the XC 101 example course"},
{"name": "fa17", "color": "gray1", "description": "Question written in Fall 2017"},
{"name": "sp18", "color": "gray1", "description": "Question written in Spring 2018"},
Expand Down
7 changes: 7 additions & 0 deletions exampleCourse/questions/examplesVariableOutput/info.json
@@ -0,0 +1,7 @@
{
"uuid": "cc99f57c-1a13-4161-b052-1cd795011d98",
"title": "Examples showing matrix and scalar output via pl-variable-output",
"topic": "Test",
"tags": ["eliving2", "Sp19", "tpl101", "v3", "numeric"],
"type": "v3"
}
98 changes: 98 additions & 0 deletions exampleCourse/questions/examplesVariableOutput/question.html
@@ -0,0 +1,98 @@
<pl-question-panel>
<p>
The questions below were designed to showcase the different features of
<a href="https://prairielearn.readthedocs.io/en/latest/elements/#pl-variable-output-element"><code>pl-variable-output</code></a>.
The goal of <a href="https://prairielearn.readthedocs.io/en/latest/elements/#pl-variable-output-element"><code>pl-variable-output</code></a>
is to allow users to easily copy parameters (scalar values or 2D matrices) for various computational tools.
</p>
</pl-question-panel>

<div class="card my-2">
<div class="card-body">
<pl-question-panel>
<p>Within this question, you can observe the default behavior of the <a href="https://prairielearn.readthedocs.io/en/latest/elements/#pl-variable-output-element"><code>pl-variable-output</code></a> element.
In particular, the default matrix output has Matlab, Mathematica, and Python tabs. The default active tab is Matlab, this can be changed using the attribute <code>default-tab</code>. This element has the ability to display any 2D matrix or scalar value and they are displayed with <code>2</code> digits after the decimal.</p>
</pl-question-panel>
<pl-variable-output>
<variable params-name="a">a</variable>
<variable params-name="d">d</variable>
<variable params-name="x">x</variable>
<variable params-name="x1">x1</variable>
<variable params-name="x2">x2</variable>
</pl-variable-output>
</div>
</div>

<div class="card my-2">
<div class="card-body">
<pl-question-panel>
<p>This example lowers the number of displayed digits to <code>0</code> for the entire element and introduces comments for each variable. This uses the optional element parameter <code>digits</code>.
</pl-question-panel>
<pl-variable-output digits="0">
<variable params-name="x" comment="This is an array">x</variable>
<variable params-name="x1" comment="This is a 2D matrix">x1</variable>
</pl-variable-output>
</div>
</div>

<div class="card my-2">
<div class="card-body">
<pl-question-panel>
<p>Under this variant, we opt to change the number of displayed digits to <code>0</code> for the entire element, but explicitly specify <code>8</code> digits for the real number <code>d</code> and <code>2</code> digits for <code>c</code> and <code>xC</code> . This uses the optional variable parameter <code>digits</code>.</p>
</pl-question-panel>
<pl-variable-output digits="0">
<variable params-name="a">a</variable>
<variable params-name="b">b</variable>
<variable params-name="d" digits="8">d</variable>
<variable params-name="c" digits="2">c</variable>
<variable params-name="xC" digits="2">xC</variable>
</pl-variable-output>
</div>
</div>

<div class="card my-2">
<div class="card-body">
<pl-question-panel>
<p>The variables in the Mathematica tab are reformatted if they are reserved in Mathematica (C, D, E, I, K, N, O). These reserved variables are appended with an <code>'m'</code> in the Mathematica tab <strong>only</strong>.</p>
</pl-question-panel>
<pl-variable-output digits="0">
<variable params-name="x" comment="The Mathematica tab adds an 'm' to the variable name">C</variable>
<variable params-name="x1">D</variable>
<variable params-name="b">E</variable>
<variable params-name="a">I</variable>
<variable params-name="b">K</variable>
<variable params-name="a">N</variable>
<variable params-name="b">O</variable>
</pl-variable-output>
</div>
</div>

<div class="card my-2">
<div class="card-body">
<pl-question-panel>
<p>The default active tab is Matlab, in this example the default active tab is Python. This is done by setting the optional parameter <code>default-tab="python"</code>.</p>
</pl-question-panel>

<pl-variable-output digits="0" default-tab="python">
<variable params-name="x1">x1</variable>
</pl-variable-output>

<p>All computational languages are displayed by default. These can be turned off using the element attributes <code>show-matlab</code>, <code>show-mathematica</code>, and <code>show-python</code>. In this example, we remove the the Matlab tab. The default active tab will be the first one in the list, in this case: Mathematica.</p>

<pl-variable-output digits="0" show-matlab="False">
<variable params-name="x1">x1</variable>
</pl-variable-output>

<p>When a tab is not displayed, the default active tab can still be set. In this case, the Mathematica tab is hidden and Python is set as the default active tab.</p>

<pl-variable-output digits="8" show-mathematica="False" default-tab="python">
<variable params-name="d">d</variable>
</pl-variable-output>

<p>Any tab can be displayed alone. In this case, the Matlab and Mathematica tabs are hidden and the Python tab is displayed alone.</p>

<pl-variable-output digits="0" show-matlab="False" show-mathematica="False">
<variable params-name="x1">x1</variable>
</pl-variable-output>
</div>
</div>