Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

- new feature: possiblity to enter µ (mu, and greek letter) in addition to u
- new feature: conversion rules: add fullsiunit beside of commonsiunit and none.
- removed: not used function get_unit_mapping(), and get_dimension_list()

### 5.2.0 (2023-03-17)
- new functions: binomialpdf() and binomialcdf()
- bugfix: gcd() now gives correct result even if one argument is 0
Expand Down
81 changes: 56 additions & 25 deletions answer_unit.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,19 @@ class answer_unit_conversion {
private $default_last_id; // Dimension class id counter.
private $default_id; // Id of the default rule.
private $default_rules; // String of the default rule in a particular format.
private $part_unit; // String of the unit of a part, given.
// @codingStandardsIgnoreLine
public static $unit_exclude_symbols = '][)(}{><0-9.,:;`~!@#^&*\/?|_=+ -';
public static $rule_exclude_symbols = '][}{><,:;`~!@#&*?|_=';
public static $prefix_scale_factors = array('d' => 1e-1, 'c' => 1e-2, 'da' => 1e1, 'h' => 1e2,
'm' => 1e-3, 'u' => 1e-6, 'n' => 1e-9, 'p' => 1e-12, 'f' => 1e-15, 'a' => 1e-18, 'z' => 1e-21, 'y' => 1e-24,
'k' => 1e3, 'M' => 1e6, 'G' => 1e9, 'T' => 1e12, 'P' => 1e15, 'E' => 1e18, 'Z' => 1e21, 'Y' => 1e24);
// For convenience, u is used for micro-, rather than "mu", which has multiple similar UTF representations.
'm' => 1e-3, 'u' => 1e-6, 'µ' => 1e-6, 'μ' => 1e-6, 'n' => 1e-9, 'p' => 1e-12,
'k' => 1e3, 'M' => 1e6, 'G' => 1e9, 'T' => 1e12, 'P' => 1e15,
'E' => 1e18, 'Z' => 1e21, 'Y' => 1e24, 'f' => 1e-15, 'a' => 1e-18, 'z' => 1e-21, 'y' => 1e-24,
'R' => 1e27, 'Q' => 1e30, 'r' => 1e-27, 'q' => 1e-30 );
// For convenience, u can be used for micro-, rather than "mu", which has multiple similar UTF representations.
// Note: All UTF representations of µ can be used too.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Note: All UTF representations of µ can be used too.
// Note: One can use GREEK SMALL LETTER MU (U+03BC) or MICRO SIGN (U+00B5).

public static $units_special = array('mol','min','cd'); /* all units >= 2 characters starting with prefix */
public static $prefix_all = 'k M G T P E Z Y m u µ μ n p f a z y R Q r q d c da h';

// Initialize the internal conversion rule to empty. No exception raised.
public function __construct() {
Expand All @@ -66,20 +73,61 @@ public function __construct() {
$this->default_mapping = null;
$this->mapping = null;
$this->additional_rules = '';
$this->part_unit = '';
}

/**
* Parse a unit into prefix and SI unit
*
* @param string $unit get the unit with prefix and SI unit, must be trimmed before
* @return string SI unit only
*/
public function parse_prefix_unit($unit) {
if (is_array($unit)) {
throw new Exception('parse_prefix_unit does not handle arrays!');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string should be in lang/en/qtype_formulas.php and imported with get_string.

return '';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return '';

Unreachable code.

}
$pattern = '/^([\wµμ]*).*/'; /* get only first word, accept also µ μ */
$replacement = '${1}';
$shu = preg_replace($pattern, $replacement, $unit);
if ( (strlen($shu) < 2) ||
(in_array($shu, static::$units_special) )) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(in_array($shu, static::$units_special) )) {
(in_array($shu, self::$units_special) )) {

return $unit;
}
foreach (static::$prefix_scale_factors as $i => $prefix) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
foreach (static::$prefix_scale_factors as $i => $prefix) {
foreach (self::$prefix_scale_factors as $i => $prefix) {

if (str_starts_with($unit,$i)) {
$unit = substr($unit, strlen($i));
break;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The break is not needed IMHO.

Suggested change
break;

}
}
return $unit;
}

/**
* It assign default rules to this class. It will also reset the mapping. No exception raised.
*
* @param string $default_id id of the default rule. Use to avoid reinitialization same rule set
* @param string $default_rules default rules
*/
public function assign_default_rules($default_id, $default_rules) {
if ($this->default_id == $default_id) {
public function assign_default_rules($default_id, $default_rules, $part_unit = '') {
if (($default_id == 2) && ($part_unit != $this->part_unit)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would probably be better to have a class constant ALL_SI_UNITS = 2 and use it here. This would make the code easier to understand in a few years.

var_dump("part_unit:", $part_unit);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var_dump("part_unit:", $part_unit);

$default_rules = '';
// $_units = $this->parse_targets($part_unit); // does not work as expected
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// $_units = $this->parse_targets($part_unit); // does not work as expected

$_units = array_map('trim', explode('=', $part_unit));
var_dump("_units after array_map/trim:", $_units);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var_dump("_units after array_map/trim:", $_units);

foreach ($_units as $_unit) {
//$_key = array_key_first($_unit);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//$_key = array_key_first($_unit);

$unit = $this->parse_prefix_unit($_unit);
if ($unit != '') {
$default_rules .= $unit . ':' . static::$prefix_all . ';';
}
}
} elseif ($this->default_id == $default_id) {
return; // Do nothing if the rules are unchanged.
}
$this->default_id = $default_id;
$this->part_unit = $part_unit;
$this->default_rules = $default_rules;
$this->default_mapping = null;
$this->mapping = null;
Expand Down Expand Up @@ -118,23 +166,6 @@ public function reparse_all_rules() {
}


// Return the current unit mapping in this class.
public function get_unit_mapping() {
return $this->mapping;
}


// Return a dimension classes list for current mapping. Each class is an array of $unit to $scale mapping.
public function get_dimension_list() {
$dimension_list = array();
foreach ($this->mapping as $unit => $class_scale) {
list($class, $scale) = $class_scale;
$dimension_list[$class][$unit] = $scale;
}
return $dimension_list;
}


/**
* Check whether an input unit is equivalent, under conversion rules, to target units. May throw
*
Expand Down Expand Up @@ -247,7 +278,7 @@ private function check_convertibility_parsed($a, $targets_list) {
* @return array(conversion factor, unit exponent) if it can be converted, otherwise null.
*/
private function attempt_conversion($test_unit_name, $base_unit_array) {
$oclass = $this->mapping[$test_unit_name];
$oclass = $this->mapping[$test_unit_name] ?? null;
if (!isset($oclass)) {
return null; // It does not exist in the mapping implies it is not convertible.
}
Expand Down Expand Up @@ -381,8 +412,8 @@ private function parse_rules(&$mapping, &$dim_id_count, $rules_string) {
throw new Exception('Syntax error of SI prefix');
} else if (count($e) == 2) {
$unit_name = trim($e[0]);
if (preg_match('/['.self::$unit_exclude_symbols.']+/', $unit_name)) {
throw new Exception('"'.$unit_name.'" unit contains unaccepted character.');
if (preg_match('/['.self::$rule_exclude_symbols.']+/', $unit_name)) {
throw new Exception('"'.$unit_name.'" rule contains unaccepted character.');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string should be in lang/en/qtype_formulas.php and imported with get_string. (I know it wasn't done like this before, but if we're at it...)

}
$unit_scales[$unit_name] = 1.0; // The original unit.
$si_prefixes = explode(' ', $e[1]);
Expand Down
31 changes: 17 additions & 14 deletions conversion_rules.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,29 @@ class unit_conversion_rules {
public function __construct() {
$this->basicunitconversionrule[0] = array(get_string('none', 'qtype_formulas'), '');
$this->basicunitconversionrule[1] = array(get_string('commonsiunit', 'qtype_formulas'), '
m: k c d m u n p f;
s: m u n p f;
g: k m u n p f;
mol: m u n p;
N: k m u n p f;
A: m u n p f;
J: k M G T P m u n p f;
m: k c d m u µ μ n p f;
s: m u µ μ n p f;
g: k m u µ μ n p f;
mol: m u µ μ n p;
N: k m u µ μ n p f;
A: k m u µ μ n p f;
J: k M G T P m u µ μ n p f;
J = 6.24150947e+18 eV;
eV: k M G T P m u;
W: k M G T P m u n p f;
eV: k M G T P m u µ μ;
W: k M G T P m u µ μ n p f;
Pa: k M G T P;
Hz: k M G T P E;
C: k m u n p f;
V: k M G m u n p f;
C: k m u µ μ n p f;
V: k M G m u µ μ n p f;
ohm: m k M G T P;
F: m u n p f;
T: k m u n p;
H: k m u n p;
Ω: u µ μ m k M G T P;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is (U+03A9 / GREEK CAPITAL LETTER OMEGA). I suggest adding Ω (U+2126 / OHM SIGN) as well.

F: m u µ μ n p f;
T: k m u µ μ n p;
H: k m u µ μ n p;
');

$this->basicunitconversionrule[2] = array(get_string('allsiunits', 'qtype_formulas'), '');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I am getting this right: You changed the conversion rules for commonsiunit, but you define allsiunits anyway. But don't the changes bring the desired functionality to commonsiunit as well? Would there be any reason for a user to stick with commonsiunit versus allsiunits?


/* You can define your own rules here, for instance:
* $this->basicunitconversionrule[100] = array(
* $this->basicunitconversionrule[1][0] + ' and your own conversion rules',
Expand Down
8 changes: 6 additions & 2 deletions lang/en/qtype_formulas.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,14 @@ functions and operators is given in the documentation.
$string['ruleid_help'] = 'This question type has a build-in unit conversion system and has basic conversion rules.

The basic one is the "Common SI unit" rules that will convert standard units
such as unit for length, say, km, m, cm and mm. This option has no
effect if no unit has been used.';
such as unit for length, say, km, m, cm and mm.
The "All SI units" rules converts all given units with its SI prefixes including u and µ (Greek letter),
see https://en.wikipedia.org/wiki/Metric_prefix.
Those options have no effect if no unit has been used.';

$string['none'] = 'None';
$string['commonsiunit'] = 'Common SI unit';
$string['allsiunits'] = 'All SI units';
$string['otherrule'] = 'Other rules';
$string['otherrule_help'] = 'Here the question\' author can define additional conversion rules for other accepted base units. See documentation for the advanced usages.';
$string['subqtext'] = 'Part\'s text';
Expand Down
2 changes: 1 addition & 1 deletion question.php
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ public function grade_responses_individually($part, $response, &$checkunit) {
// Step 2: Use the unit system to check whether the unit in student responses is *convertible* to the true unit.
$conversionrules = new unit_conversion_rules;
$entry = $conversionrules->entry($part->ruleid);
$checkunit->assign_default_rules($part->ruleid, $entry[1]);
$checkunit->assign_default_rules($part->ruleid, $entry[1], $part->postunit);
$checkunit->assign_additional_rules($part->otherrule);
$checked = $checkunit->check_convertibility($postunit, $part->postunit);
$cfactor = $checked->cfactor;
Expand Down
2 changes: 1 addition & 1 deletion questiontype.php
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ public function validate_instantiation($form, &$validanswers) {
if ($entry === null || $entry[1] === null) {
throw new Exception(get_string('error_ruleid', 'qtype_formulas'));
}
$unitcheck->assign_default_rules($ans->ruleid, $entry[1]);
$unitcheck->assign_default_rules($ans->ruleid, $entry[1], $ans->postunit);
$unitcheck->reparse_all_rules();
} catch (Exception $e) {
$errors["ruleid[$idx]"] = $e->getMessage();
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
defined('MOODLE_INTERNAL') || die();

$plugin->component = 'qtype_formulas';
$plugin->version = 2023031700;
$plugin->version = 2023040500;

$plugin->cron = 0;
$plugin->requires = 2017111300;
Expand Down