-3

I have been looking for a good, general design pattern to implement simple mathematical structures where functions have the following properties:

  • know which parameters they contain, parameters are "fixed"
  • can be called with variable values as arguments
  • are defined in terms of other functions, which have the same properties as the aforementioned two.
  • prevent repetition of parameter declaration, even if some super functions contain the same parameters as their respective sub-functions.

I found many useful resources but still have some issues.

Mathematical functions can be defined as classes as explained here where parameters are attributes and the __call__ method is used to make the class instances callable with variables as arguments. Here is a simple mathematical structure which is modelled quite well using this approach in python

Mathematical structure

T = c_v*t/(a*h)**2
U = (T**3/(T**3+0.5))

Sample code

class T:
    def __init__(self, c_v, a, h):
        self.c_v = c_v
        self.a = a
        self.h = h
    def __call__(self, t):
        return self.c_v*t/(self.a*self.h)**2

class U(T):

    def __call__(self, t):
        T = super().__call__(t)
        return (T**3/(T**3+0.5) )**(1/6)

The following code will then give a mathematically correct result:

>>> U_func = U(1.5E-7, 0.5, 12)
>>> U_func(100*24*60*60)

This works quite well, but it could be that class U is dependent on several other function objects. Inheritance here will not work, as all "super" functions implement the same method to do their arithmetic (__call__). Composition also presents some challenges which I will not get into here. My conclusion is that the above approach will not solve my problem, although it looks nice.

I then did some reading on several patterns like: - Strategy - Factory - Composite

The Composite Pattern showed some promise as it does support tree-structures, I also found out that simple arithmetic is in fact a tree structure as coined by the phrase "Binary Expression Trees". What I am trying to do is related to binary expression trees but is in fact not at the +-/* operator level.

Does anyone understand or identify with what I am trying to achieve? Am I wasting my time here? Is there a one-size fits all design pattern or should I use a combination therewith for this case?

5
  • 2
    Possible duplicate of Choosing the right Design Pattern
    – gnat
    Oct 20, 2019 at 10:10
  • 1
    Umm... Absolutely not. My question is much more detailed and refers to selecting the right design patttern for a very specific problem which I describe quite extensively. I have already seen the answers to that question and they don't help at all.
    – user32882
    Oct 20, 2019 at 10:11
  • @user32882 "Is there a one-size fits all design pattern ..." That's very unlikely. Oct 20, 2019 at 10:18
  • @πάνταῥεῖ you're probably right. But in that case what combination of patterns should I use?
    – user32882
    Oct 20, 2019 at 10:25
  • 3
    "Does anyone understand or identify with what I am trying to achieve?" - this is pretty hard without knowing what's your overall goal. For what purpose is this required ? What's the actual use case you try to implement? And why not simply use Python's formula syntax, what's your problem with this? Currently, this question looks very much like an XY problem. Voting to close as "unclear" until we get a better explanation.
    – Doc Brown
    Oct 20, 2019 at 12:01

2 Answers 2

3

I think that you need some variation of the interpreter pattern.

Your mathematical construct would be a aset of AbstractExpression:

  • A function would be represented by a concrete TerminalExpression if it is self sufficient, and the values of the parameters would be defined in the context (normal case) or at construction of the object (if you really want to fix the values).

  • The function would be represented by a concrete NonTerminalExpression if it relies on other functions. These would then be an AbstractExpression on their own, that would be injected at construction.

Each AbstractExpression can be evaluated by invoking a method interpret(). In your case, I could imagine that interpret returns a result value. interpet() is provided with "context" parameters to be used for the evaluation.

This is very powerful, since the context could be a list of parameters (in the order expected by your function), or it could be a more elaborate structure, providing a symbol table that maps named parameters to values (so you do not need to fix the parameters at construction).

Edit: how it differs from the composite

When we look at the structure of the interpreter, it appears to look very similar to the composite's one.

The composite pattern is a structural patterns. Its intent is to represent part-whole hierarchy in a tree structure, and let the client treat individual objects and composed object in a uniform way.

The interpreter pattern also looks like a tree structure. But the interpreter is a behavioral pattern with a different intent. It aims at representing a language structure (here the relatively simple mathematical language for function composition) and to use this representation to execute the language.

Beyond the difference in intent, you will for example note that:

  • the composite does not make use of an execution "context" provided by the client. Because composite only addresses structural concerns.
  • the composite allows to manage children structure dynamically, whereas the interpreter does not define how sub-expressions are managed and let the responsibility of building it to the client.
  • the interpreter allows to have more complex graphs than trees, since you could reuse the same sub-expression in several places.

These patterns are not incompatible. In fact, both could even be combined to address simultaneously structural and behavioural concerns.

3
  • 1
    @user32882 It's a little bit too long for a comment. I've edited to clarify the difference
    – Christophe
    Oct 20, 2019 at 12:27
  • Sorry I just looked into this and I think that Interpreter is way too out there for what I am trying to do. It feels like reinventing the wheel. Is it really necessary to define an entire symbolic grammar to be able to tackle this problem? I just need something which is a bit better than calling a function with other functions in their arguments. Simple really.
    – user32882
    Oct 20, 2019 at 12:54
  • 1
    @user32882 I think that you should not look at the classical examples of using interpreter to execute expression based on the symbolic grammar. These examples are not appropriate for you. Instead, give it a try the way I've described it: start simple, defining an AbstractExpression; then define your class T as being a specialisation of it. The context can be the list of fixed parameters that you'd expect (e.g. first value as c_v, second value a, third value h). Then define class U as another specialisation of AbstractExpression, that constructs its own local T object.
    – Christophe
    Oct 20, 2019 at 14:53
2

You could use lambdas in python.

add1=lambda x,y:x+y
mul1=lambda x,y:x*y
root=lambda x,y,z:mul1(add1(x,y),z)
result=root(2,3,4)
print(result)

This requires you to roll up the tree for whatever you want.

Maybe you can generate the python source from a data file.

Maybe there is something section 2.3.3 of volume 1 of TAOCP that you would find useful.

Or maybe you need to explicitly build a tree (see Java example below)

import java.util.*;
import java.util.function.*;
class SE400040 {
    static class Node {
        Node(Function<List<Node>,Double> function,List<Node> children) {
            this(function,children,"");
        }
        Node(Function<List<Node>,Double> function,List<Node> children,String name) {
            this.object=function;
            this.children=children;
            this.name=name;

        }
        Node(Double d) {
            object=d;
            children=Collections.emptyList();
            name=d.toString();
        }
        @Override public String toString() {
            return children.size()==0?object.toString():name;
        }
        final String name;
        final List<Node> children;
        final Object object; // number or function
    }
    static Double valueOf(Node node) {
        Double x;
        if(node.object instanceof Double) x=(Double)node.object;
        else if(node.object instanceof Function) {
            Function<List<Node>,Double> f=(Function<List<Node>,Double>)node.object;
            x=f.apply(node.children);
        } else throw new RuntimeException("oops");
        System.out.println("valueOf returning: "+x);
        return x;
    }
    static Function<List<Node>,Double> add=doubles-> {
        Double x=valueOf(doubles.get(0));
        Double y=valueOf(doubles.get(1));
        System.out.println("add returning: "+(x+y));
        return x+y;
    };
    static Function<List<Node>,Double> multiply=doubles-> {
        Double x=valueOf(doubles.get(0));
        Double y=valueOf(doubles.get(1));
        System.out.println("multiply returning: "+(x*y));
        return x*y;
    };
    public static void main(String[] args) {
        Node node1=new Node(2.);
        Node node2=new Node(3.);
        Node node3=new Node(4.);
        List<Node> arguments=new ArrayList<>();
        arguments.add(node1);
        arguments.add(node2);
        Node added=new Node(add,arguments,"add");
        List<Node> arguments2=new ArrayList<>();
        arguments2.add(added);
        arguments2.add(node3);
        Node multiplied=new Node(multiply,arguments2,"multiply");
        System.out.println("------------");
        System.out.println(""+node1+valueOf(node1));
        System.out.println("------------");
        System.out.println(""+added+valueOf(added));
        System.out.println("------------");
        System.out.println(""+multiplied+valueOf(multiplied));
        System.out.println("------------");
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.