maths / moodle-qtype_stack

Stack question type for Moodle
GNU General Public License v3.0
140 stars 148 forks source link

Unable to use var as a unit #875

Open Famondir opened 1 year ago

Famondir commented 1 year ago

Creating questions in the field of electonics engineering I found it problematic that you can't use the term var or VAr as a unit for reactive power. It is pretty close related to the issue #775. var, VAr or VAR are all forbidden. I'm not sure if it is a European wide law to use it stated in the European units of measurement directives or if it is only encouraged being used in Germany because I myself am no expert in electronics but creating the quiz questions for our engineers.

Famondir commented 1 year ago

For now I've written a function to handle string inputs (see below). Two questions came up:

  1. Is there a STACK function that takes a string like "var" and creates a unit var? Or there none because of the same reason eval() is forbidden?
  2. Is there a way to raise custom errors in STACK with more helpful texts as Division/Zero because of my proxy error? (The unfortunately function error is forbidden as well.)
getValue(x):= (  
    starSplit: split(x, "*"),
    /* behandelt nur Eingaben der Form Wert*Einheit
    oder Wert (ohne Einheit), aber keine zusammengesetzten Einheiten */
    if (length(starSplit) > 2) then (1/0),

    num: starSplit[1], 
/*    unit: starSplit[2], */

    value: 0,
    sign: 0,
    pointSplit: split(num, "."),
    /* Der Wert darf nur ein Dezimaltrennzeichen besitzen */
    if (length(pointSplit) > 2) then (1/0),

    if (charat(num,1)=".") then (
        push("0", pointSplit)       
    ),

    lNum: pointSplit[1],

    /* Ziffern vor dem Dezimaltrennzeichen */   
    lNumLength: length(charlist(lNum)),
    if (charat(lNum,1)="-") then (sign: 1),
    i:lNumLength,
    while i>sign do (
        c: charat(lNum,i),
        /* Hier sollten nur noch ZIffern zu finden sein */
        if (not digitcharp(c)) then (1/0),
        value: value+10^(lNumLength-i)*(cint(c)-48),
        i: i-1
    ),

    /* Ziffern nach dem Dezimaltrennzeichen */
    if (length(pointSplit) > 1) then (
        rNum: pointSplit[2],
        rNumLength: length(charlist(rNum)),
        i:1,
        while i<=rNumLength do (
        c: charat(rNum,i),
        /* Hier sollten nur noch ZIffern zu finden sein */
        if (not digitcharp(c)) then (1/0),
        value: value+10^(-i)*(cint(c)-48),
        i: i+1
        )
    ),

    /* Vorzeichen setzen */
    value: value*(-1)^sign
);

getUnit(x):= (
    starSplit: split(x, "*"),
    /* behandelt nur Eingaben der Form Wert*Einheit, 
    aber keine zusammengesetzten Einheiten */
    if (not length(starSplit) = 2) then (1/0),

/*    num: starSplit[1], */
    unit: starSplit[2]    
);

evalACPowerUnit(x):= (
    value: getValue(x),
    unit: getUnit(x),

    /* Wählt die passende Einheit aus. */
    if (unit = "P") then (
        res: stackunits(value, P)
    ) else if (unit = "VA") then (
        res: stackunits(value, VA)
    ) else if (unit = "var" or unit = "Var" or unit = "VAr" or unit = "VAR") then (
        res: stackunits(value, var)
    ) else (1/0)
);
aharjula commented 1 year ago
  1. There is no function (currently), but if you look at how the lists present in maximalocal.mac have been constructed. The lists are named stack_unit_... and are used here. You can make an educated guess of what you should add to those lists to get your unit working on the CAS side. Don't mess with your STACK installation, you should be able to do this at the question-variables level, assuming the STACK version is not too old.

As to 2. I have no idea. But you might end up with better results by adding your unit to those lists and then using the built-in functions to deal with the separation of units from the numerical values. When you use string inputs, you lose all the normal validation error messages, which is a problem if you want to have nice validation messages/feedback for students. Triggering actual errors in the PRT happens too late for generating validation messages, validation-related issues need to happen in the validation, and anything done at the feedback variables is just too late. There currently exists no way to override validation logic (#870), but even if such things did exist, dealing with units should happen in the unit system and should not require custom validation logic. If you want to return something more sensible like und from that function, then you can use that in the PRT to identify the failure and give it zero penalty and suitable guidance, but even then, it is not exactly nice for analytical purposes as anything ending up to PRTs is assumed to be valid in most pre-made logic... Also if you want to error out from the function and react to that outside of it, you can try errcatch() if your STACK is new enough.

In the larger picture, the unit system is starting to get old, and there are various ideas about how it could be modified to allow simpler configuration at the question level. At this time, there are no real means for that as the unit system has two places where it needs to work firstly it needs to do the easy conversions and presentation issues on the CAS side, which is the lesser issue and can be done by adjusting those lists, secondly however we also need to know the units on the PHP side as parts of the validation of student input happens on that side, and that side needs to know when something is a variable and when it could be an identifier for a unit and at this point the bit of logic that "knows" these things has no idea about any modifications to the defined lists at the CAS side.

Obviously, direct modification of those lists will not make it any simpler for the PHP side validation to keep track of the units in play (doing an extra CAS call to ask for the status after some arbitrary logic is not something we want to do), so I see that the lists will eventually become read-only, and there will be functions to modify them so that we can capture the modifications and collect them for the PHP side. Those functions may have additional special rules related to them for example, they might not work (would be marked invalid in the question validation) with non-atomic (i.e. direct values, no references) arguments, or they might not be allowed to be placed inside function definitions or conditional statements to ensure that they are static and can be easily detected by parsing. It is also possible that these "functions" would not actually be functions they might be annotations to further separate their syntax and use in arbitrary logic so that there are no misunderstandings of what they do and when.

The reason why none of those ideas has been done already is largely related to the large refactoring projects that have been in play since STACK 4.3, particularly the infrastructure for "compiling" questions is something that most of those ideas need, and we have not had the base to build on, now that we have that base in 4.4 we can start to look at this, but we really want to first see that that base stabilises before we start to stack too many new things on it. However, this is a thing that is relatively unconnected to some of the largest ideas in the pipeline yet still has some interesting similarities in the solutions required, so it is likely that improvements related to this are coming sooner than some of those large ideas, just because this is a nice and well-defined issue we can test against before those larger ones start to take shape.

Famondir commented 1 year ago

Thank you for your elaborated answer. I had a look at your suggestion at 1. and tried out the recommandation in the docs. I missed the hint to include the new variable in the allowed words section before. I agree that letting STACK handle the input is much better than using a custom string based function.

Famondir commented 6 months ago

I have another question regarding custom units. It seems that prefixes like u, m, k don't work for those right now. Is this true or do I miss something here?

aharjula commented 6 months ago

If you look here you will notice that the prefixes are only declared for a particular set of units. The function after that does the actual conversions and there is also a separate bit on the PHP side that is aware of the prefixes. So, these prefixes are not automagically defined for any new units that come in through any other route.

Now, if one were to modify (in question code) the lists that the first function linked above uses and calls it again to override the original definitions, then maybe one could get some interesting results, but that would still not affect the PHP side, and thus, validation messages might be a bit incomplete.