diff --git a/CHANGES.md b/CHANGES.md index f7849e05..b3d1c1b6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/answer_unit.php b/answer_unit.php index a0ace373..e96fa252 100644 --- a/answer_unit.php +++ b/answer_unit.php @@ -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. + 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() { @@ -66,8 +73,35 @@ 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!'); + return ''; + } + $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) )) { + return $unit; + } + foreach (static::$prefix_scale_factors as $i => $prefix) { + if (str_starts_with($unit,$i)) { + $unit = substr($unit, strlen($i)); + break; + } + } + return $unit; + } /** * It assign default rules to this class. It will also reset the mapping. No exception raised. @@ -75,11 +109,25 @@ public function __construct() { * @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)) { + var_dump("part_unit:", $part_unit); + $default_rules = ''; + // $_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); + foreach ($_units as $_unit) { + //$_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; @@ -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 * @@ -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. } @@ -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.'); } $unit_scales[$unit_name] = 1.0; // The original unit. $si_prefixes = explode(' ', $e[1]); diff --git a/conversion_rules.php b/conversion_rules.php index e5663672..62029694 100644 --- a/conversion_rules.php +++ b/conversion_rules.php @@ -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; +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'), ''); + /* You can define your own rules here, for instance: * $this->basicunitconversionrule[100] = array( * $this->basicunitconversionrule[1][0] + ' and your own conversion rules', diff --git a/lang/en/qtype_formulas.php b/lang/en/qtype_formulas.php index 2ac0d295..c0786af6 100644 --- a/lang/en/qtype_formulas.php +++ b/lang/en/qtype_formulas.php @@ -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'; diff --git a/question.php b/question.php index 6593e793..abebfc02 100644 --- a/question.php +++ b/question.php @@ -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; diff --git a/questiontype.php b/questiontype.php index df634102..be36499c 100644 --- a/questiontype.php +++ b/questiontype.php @@ -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(); diff --git a/version.php b/version.php index ceabbfa4..84dc03d8 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'qtype_formulas'; -$plugin->version = 2023031700; +$plugin->version = 2023040500; $plugin->cron = 0; $plugin->requires = 2017111300;