2 OOP in Maple

2.1 How to use new object method calling in Maple 2021?
2.2 How to make a constructor for an Object?
2.3 How to make different constructors for an Object?
2.4 How to do OOP inheritance?
2.5 How to extend a base class and override its method with different one?
2.6 How to extend a class and call base class function from the extended class??
2.7 How to use object as user defined record inside a proc?
2.8 How to make copy of list of objects?
2.9 How to use OOP to implement ode solver?
2.10 How to make a complete OOP ode solver in Maple?

2.1 How to use new object method calling in Maple 2021?

In Maple 2021, it is now possible to use object:-method(arg) notation. This makes is easier to use OOP in maple. To do this, use _self as follows

restart; 
 
module person() 
 option object; 
 local name::string:=""; 
 local age::integer:=0; 
 
 export get_name::static:=proc(_self,$) 
   return _self:-name; 
 end proc; 
 
 export set_name::static:=proc(_self,name::string,$) 
   _self:-name:=name; 
 end proc; 
 
 export get_age::static:=proc(_self,$) 
   return _self:-age; 
 end proc; 
 
 export set_age::static:=proc(_self,age::integer,$) 
   _self:-age:=age; 
 end proc; 
 
end module;
 

And now make an object and use it as follows

o:=Object(person) 
                  o := Object<<1846759887808>> 
 
o:-get_name(); 
             "" 
 
o:-get_age(); 
             0 
 
o:-set_name("joe doe"); 
o:-get_name(); 
           "joe doe"
 

2.2 How to make a constructor for an Object?

Add ModuleCopy proc in the class. This will automatically be called to initialize the object.

Here is an example

restart; 
 
module ODE() 
 option object; 
 local ode:=NULL; 
 local y::symbol; 
 local x::symbol; 
 local sol; 
 
 export ModuleCopy::static := proc( _self::ODE, proto::ODE, ode, func, $ ) 
    print("Initilizing object with with args: ", [args]); 
    _self:-ode:= ode; 
    _self:-y:=op(0,func); 
    _self:-x:=op(1,func); 
 end proc; 
 
 export dsolve::static:=proc(_self,$) 
   _self:-sol := :-dsolve(ode,y(x)); 
 end proc; 
 
 export get_sol::static:=proc(_self,$) 
   return sol; 
 end proc; 
 
end module;
 

And now make an object and use it as follows

o:=Object(ODE, diff(y(x),x)+y(x)=sin(x), y(x)); 
o:-dsolve(): 
o:-get_sol(); 
 
        #y(x) = -1/2*cos(x) + 1/2*sin(x) + exp(-x)*_C1
 

So a constructor just makes it easier to initialize the object without having to make a number of set() calls to initialize each memeber data.

2.3 How to make different constructors for an Object?

This is done using overload with different ModuleCopy proc in the class.

Here is an example. Lets make a constructor that takes an ode and initial conditions, and one that only takes an ode with no initial conditions.

restart; 
 
module ODE() 
 option object; 
 local ode:=NULL; 
 local y::symbol; 
 local x::symbol; 
 local ic:=NULL; 
 local sol; 
 
 export ModuleCopy::static:= overload( 
    [ 
       proc( _self::ODE, proto::ODE, ode, func, $ ) option overload; 
          _self:-ode:= ode; 
          _self:-y:=op(0,func); 
          _self:-x:=op(1,func); 
       end proc, 
 
       proc( _self::ODE, proto::ODE, ode, func, ic, $ ) option overload; 
          _self:-ode:= ode; 
          _self:-y:=op(0,func); 
          _self:-x:=op(1,func); 
          _self:-ic :=ic; 
       end proc 
    ] 
 ); 
 
 export dsolve::static:=proc(_self,$) 
    if evalb(ic=NULL) then 
       sol := :-dsolve(ode,y(x)); 
    else 
       sol := :-dsolve([ode,ic],y(x)); 
    fi; 
 end proc; 
 
 export get_sol::static:=proc(_self,$) 
   return sol; 
 end proc; 
 
end module;
 

And now use it as follows

o:=Object(ODE, diff(y(x),x)+y(x)=sin(x), y(x), y(0)=0); 
o:-dsolve(): 
o:-get_sol(); 
 
         #y(x) = -1/2*cos(x) + 1/2*sin(x) + 1/2*exp(-x) 
 
o:=Object(ODE, diff(y(x),x)+y(x)=sin(x), y(x)); 
o:-dsolve(): 
o:-get_sol(); 
 
    #y(x) = -1/2*cos(x) + 1/2*sin(x) + exp(-x)*_C1
 

2.4 How to do OOP inheritance?

In the child class you want to extend from the parent class, add option object(ParentName);

Here is an example

restart; 
module ODE() 
   option object; 
   local ode; 
 
   export set_ode::static:=proc(_self,ode,$) 
     _self:-ode :=ode; 
   end proc; 
 
  export get_ode::static:=proc(_self,$) 
     return _self:-ode; 
   end proc; 
end module; 
 
#create class/module which extends the above 
module second_order_ode() 
   option object(ODE); 
   export get_ode_order::static:=proc(_self,$) 
      return 2; 
   end proc; 
end module;
 

In the above second_order_ode inherts all local variables and functions in the ODE class and adds new proc. Use as follows

o:=Object(second_order_ode); #create an object  instance 
o:-set_ode(diff(y(x),x)=sin(x)); 
o:-get_ode(); 
o:-get_ode_order();
 

Note that the child class can not have its own variable with the same name as the parent class. This is limitation. in C++ for example, local variables in extended class overrides the same named variable in the parent class.

Even if the variable have different type, Maple will not allow overriding. For example, this will fail

restart; 
module ODE() 
   option object; 
   local ode; 
   local id::integer; 
 
   export set_ode::static:=proc(_self,ode,$) 
     print("Enter ode::set_ode"); 
     _self:-ode :=ode; 
   end proc; 
 
  export get_ode::static:=proc(_self,$) 
     return _self:-ode; 
   end proc; 
end module; 
 
module second_order_ode() 
   option object(ODE); 
   local id::string; 
   export get_ode_order::static:=proc(_self,$) 
      return 2; 
   end proc; 
end module; 
 
    Error, (in second_order_ode) local `id` is declared more than once
 

There might be a way to handle this, i.e. to somehow exlicitly tell Maple to override parant class proc or variable name in the child. I do not know now. The above is using Maple 2021.1

2.5 How to extend a base class and override its method with different one?

This is called polymorphism in OOP. This is a base class animal_class which has make_sound method. This method acts just as a place holder (interface), which the extending class must extends (override) with an actual implementation.

The class is extended to make cat class and implementation is made.

module animal_class() 
 option object; 
 
 export make_sound::static:=proc(_self,$) 
     error("Not implemented, must be overriden"); 
 end proc; 
end module; 
 
%--------------- 
module cat_class() 
 option object(animal_class); 
 
 make_sound::static:=proc(_self,$)  #note. DO NOT USE export 
     print("mewooo"); 
 end proc; 
 
end module;
 

And now

o:=Object(animal_class); 
o:-make_sound(); 
 
Error, (in anonymous procedure) Not implemented, must be overriden
 

The above is by design. As the animal_class is meant to be extended to be usable.

my_cat:=Object(cat_class); 
my_cat:-make_sound(); 
 
                            "mewooo"
 

So the base class can have number of methods, which are all meant to be be have its implementation provided by an extending class. Each class which extends the base class must provide implementation.

2.6 How to extend a class and call base class function from the extended class??

Once a base class is extended, all methods in the base class become part of the extending class. So to call a base class method just use same way as if calling any other method in the extending class itself.

Here is an example.

module person() 
    option object; 
    local age::integer:=100; 
 
    export ModuleCopy::static:= proc(_self,proto::person, age::integer,$) 
           _self:-age := age; 
    end proc; 
 
    local base_class_method::static:=proc(_self,$) 
        print("In base class method..."); 
    end proc; 
 
 
end module; 
 
#---- extend the above class 
module young_person() 
   option object(person); 
   export process::static:=proc(_self,$) 
      print("In young_person process"); 
     _self:-base_class_method(); 
   end proc; 
end module;
 

Here is an example of usage

o:=Object(young_person,20); 
o:-process() 
 
                   "In young_person process" 
                   "In base class method..."
 

The above in Maple 2023.1

2.7 How to use object as user defined record inside a proc?

A Maple Object can be used a record type in other languags, such as Ada or Pascal. This example shows how to define a local type inside a proc and use it as record.

restart; 
foo:=proc(the_name::string,the_age::integer)::person_type; 
     local module person_type() #this acts as a record type 
         option object; 
         export the_name::string; 
         export the_age::integer; 
     end module; 
 
     local person::person_type:=Object(person_type); 
 
     person:-the_name:=the_name; 
     person:-the_age:=the_age; 
     return person; 
 end proc; 
 
o:=foo("joe doe",100); 
 
o:-the_name; 
                           "joe doe" 
o:-the_age; 
                              100
 

In the above person is local variable of type person_type. In the above example, the local variable was returned back to user. But this is just an example. One can declare such variables and just use them internally inside the proc only. This method helps one organize related variables into one record/struct like type. The type can also be made global if needed.

2.8 How to make copy of list of objects?

Suppose we have list L1 of objects and we want to copy this list to another list, say L2. If we just do L2 = L1 then this will not make an actual copy as any changes to L1 are still reflected in L2. The above only copies the reference to the same list.

To make a physical copy, need to use the copy command as follows

restart; 
 
module person_type() 
  option object; 
  export the_name::string:="doe joe"; 
  export the_age::integer:=100; 
end module; 
 
L1:=[]; 
for N from 1 to 5 do 
    o:=Object(person_type); 
    o:-the_name:=convert(N,string); 
    L1:= [ op(L1), o]; 
od: 
 
L2:=map(Z->copy(Z),L1):
 

Now making any changes to L1 will not affect L2. If we just did L2 = L1 then both will share same content which is not what we wanted.

2.9 How to use OOP to implement ode solver?

This is a basic example of using OOP in Maple to implement an ode solver. There is a base module called ode_base_class (I will be using class instead of module, as this is more common in OOP)

This program will for now support first order and second order ode’s only.

The base ode class will contain the basic operations and data which is common to both first order and second order ode’s.

Next, we will make a first order ode class, and second order ode class. Both of these will extend the base ode class.

Next, we will have more ode classes. For example, for first order ode, there will be linear first order ode class, and separable first order ode class, and Bernoulli ode class and so on. Each one these classess will extend the first order ode class which in turn extends the base ode class.

Same for second order ode’s. There will be second order constant coefficients ode class, and second order variable coefficient ode class and so on. Each one of these classes will extend the second order ode class which in turn extends the base ode class.

Let base_ode_class be the base of an ode class which will contain all the neccessary methods that are generic and applicable to an ode of any order and type.

These can be the ode expression itself, its order and any other data and operations which applicable to any ode type.

Now we want to create a first order class. This will have its own operations and private variables that are specific and make sense only to any first order ode.

Then these will be the first order separable ode class, which has methods that implement solving the ode using separable method and has other methods which makes sense only for first order separable ode. The following diagram is partial illustration of the is-A relation among possible classes.

First we define the base ode class and define private variables and method that are common to any ode type.

restart; 
 
module base_ode_class() 
  option object; 
  local the_ode; 
  local the_order::integer; 
 
  export get_ode::static:=proc(_self,$) 
         RETURN(_self:-the_ode); 
  end proc; 
 
  export get_order::static:=proc(_self,$) 
         RETURN(_self:-the_order); 
  end proc; 
 
end module;
 

Note that the base ode class does not have constructor. Since it is meant to be extended only.

The following is the first order ode class.

module first_order_ode_class() 
  option object(base_ode_class); 
 
  local  initial_conditions; 
  local  solution_found; 
 
  export get_IC::static:=proc(_self,$) 
         RETURN(_self:-initial_conditions); 
  end proc; 
 
  export get_solution::static:=proc(_self,$) 
         RETURN(_self:-solution_found); 
  end proc; 
 
  export verify_solution::static:=proc(_self,$)::truefalse; 
      #code to verify if the solution found is valid or not 
      #using odetest() 
  end proc; 
 
end module;
 

The following is the first order separable ode class which extends the above first order ode class.

module first_order_separable_ode_class() 
  option object(first_order_ode_class); 
  local f,g;  #ode of form  y'=f(x)*g(y) 
 
  export ModuleCopy::static:= proc(_self,proto::first_order_separable_ode_class, 
                                   ode, IC,$) 
      _self:-the_ode  := ode; 
      _self:-initial_conditions := IC; 
  end proc; 
 
  export dsolve::static:=proc(_self,$) 
         #solve the ode for now we will use Maple but in my code 
         #I have my own solver ofcourse. 
         _self:-solution_found:=  :-dsolve([_self:-the_ode, _self:-initial_conditions]); 
  end proc; 
end module;
 

In the above, when we create a instance of first_order_separable_ode_class then it now have the whole chain of classes into one. i.e. first order separable class extending the first order class which in turn extends the base ode class. For example

o:=Object(first_order_separable_ode_class,diff(y(x),x)=3*sin(x)*y(x),y(0)=1) 
o:-dsolve(); 
o:-get_solution() 
       #y(x) = exp(3)*exp(-3*cos(x)) 
o:-get_ode() 
       #diff(y(x), x) = 3*sin(x)*y(x)
 

The above calls will all work, even though the first order separable class has no method in it called get_ode but it extends a class which does, hence it works as it.

Now we will do the same for second order ode’s.

Advantage of this design, is that methods in base classes that are being extended can be reused by any class which extends them. Only methods that applies and specific to the lower level classes need to be implemented.

As we add more specific solvers, we just have to extend the base classes and the new solvers just need to implement its own specific dsolve and any specific methods and data that it needs itself.

Ofcourse in practice the above design is not complete as is. The user should not have to specify which class to instantiate, as user does not care what the ode type or class it is. They just want to to do

o:=Object(ode_class,diff(y(x),x)=3*sin(x)*y(x),y(0)=1) 
o:-dsolve(); 
o:-get_solution()
 

To solve this problem we have to make a factory method which is called to make the correct instance of the class and return that to the user. The factory method figures out the type of ode and it creates the correct instance of the correct class and returns that. So the call will become

o := make_ode_object( diff(y(x),x)=3*sin(x)*y(x), y(0)=1) 
o:-dsolve(); 
o:-get_solution()
 

The function make_ode_object above is the main interface the user will call to make an ode object.

This will be explained next with examples. One possibility is to make the factory function a global function or better, a public method in a utility module. For now, it is given here as stadalone function for illustration. The user calls this method to make an object of the correct instance of the ode. Here is complete implementation of all the above including the factory method.

#factory method. Makes objects for users 
make_ode_object:=proc(ode::`=`,func::function(name)) 
   local x,y,the_order; 
   y:=op(0,func); 
   x:=op(1,func); 
   the_order := PDEtools:-difforder(ode,x); 
   if the_order=1 then 
        RETURN(first_order_ode_class:-make_ode_object(ode,func)); 
   elif the_order=2 then 
        #RETURN(second_order_ode_class:-make_ode_object(ode,func)); 
        #implement later 
        NULL; 
   else 
        error "Only first and second order ode's are currently supported"; 
   fi; 
 
end proc: 
 
######################### 
module base_ode_class() 
  option object; 
  local the_ode; 
  local the_order::integer; 
 
  #methods bound to the object 
  export get_ode::static:=proc(_self,$) 
         RETURN(_self:-the_ode); 
  end proc; 
 
  export get_order::static:=proc(_self,$) 
         RETURN(_self:-the_order); 
  end proc; 
 
end module: 
################## 
module first_order_ode_class() 
  option object(base_ode_class); 
 
  local  initial_conditions; 
  local  solution_found; 
 
  #public factory method not bound to the object. 
  export make_ode_object:=proc(ode::`=`,func::function(name)) 
     local x,y,ode_type::string; 
     y:=op(0,func); 
     x:=op(1,func); 
     ode_type:="separable"; #code here which determined first order ode type 
     if ode_type="separable" then 
        RETURN( Object(first_order_separable_ode_class,ode,func)); 
     elif ode_type="linear" then 
         RETURN( Object(first_order_linear_ode_class,ode,func)); 
     fi; 
     #more ode types added here 
  end proc; 
 
  #methods bound to the object 
  export get_IC::static:=proc(_self,$) 
         RETURN(_self:-initial_conditions); 
  end proc; 
 
  export get_solution::static:=proc(_self,$) 
         RETURN(_self:-solution_found); 
  end proc; 
 
  export verify_solution::static:=proc(_self,$)::truefalse; 
      #code to verify if the solution found is valid or not 
      #using odetest() 
  end proc; 
 
end module: 
 
################## 
module first_order_separable_ode_class() 
  option object(first_order_ode_class); 
  local f,g;  #ode of form  y'=f(x)*g(y) 
 
  export ModuleCopy::static:= proc(_self,proto::first_order_separable_ode_class,ode,func::function(name),$) 
      _self:-the_ode  := ode; 
  end proc; 
 
  export dsolve::static:=proc(_self,$) 
         #print("Enter first_order_separable_ode_class:-dsolve"); 
         #solve the ode for now we will use Maple but in my code 
         #I have my own solver ofcourse. 
         _self:-solution_found:=  :-dsolve(_self:-the_ode); 
         NULL; 
  end proc; 
end module:
 

It is used as follows

o:=make_ode_object(diff(y(x),x)=sin(x)*y(x),y(x)); 
o:-dsolve(); 
o:-get_solution(); 
                    y(x) = c__1 exp(-cos(x))
 

2.10 How to make a complete OOP ode solver in Maple?

Here, I will start making a complete small OOP ode solver in Maple.

At each step more classes are added and enhanced until we get a fully working small ode solver based on OOP design that solves a first and second order ode, this is to show how it all works. Another solvers can be added later by simply extending the base class.

The base class is called Base_ode_class. There will be Second_order_ode_class and First_order_ode_class and these classes extend the base ode class. We can later add higher_order_ode_class.

Next, there are different classes which extend these. There is First_order_linear_ode_class and First_order_separable_ode_class and so on, and these extend the First_order_ode_class.

For example, if a user wanted to solve a first order ode which happend to be say separable, then object of class First_order_separable_ode_class will be created and used.

Since the user does not know and should not know what object to create, then the factory class will be used. The factory class is what the user initially calls to make the ode object.

It is the factory class which determines which type of class to instantiate based on the ode given.

The factory class is singleton (standard module in Maple, not of type object), which has the make_ode method which is called by the user. This method parses the ode and determines its order and then based on the order determine which subclass to use, and then instantiate this and returns the correct object to the user to use.

This object will have the dsolve method and other methods the user can use on the object.

The make_ode method in the factory module accepts only the ode itself the function such as \(y(x)\). A typical use is given below

ode :=  ODE_factoy_class:-make_ode( diff(y(x),x)=sin(x), y(x) ); 
ode:-set_IC(....); 
ode:-set_hint("the hint"); 
..... 
 
ode:-dsolve();  #solves the ode 
ode:-is_solved(); #checks if ode was successfully solved 
ode:-verify_sol(); #verifies the solution using maple odetest() 
ode:-is_sol_verified(); #asks if solution is verified 
ode:-get_number_of_sol(); #returns number of solutions, 1 or 2 etc... 
ode:-get_sol(); #returns the solutions found in list 
#and more method 
...
 

Examples at the end will show how all the above works on actual odes’s.

The initial call to make an ode does not have initial conditions, or hint and any other parameters. This is so to keep the call simple. As the factory method only makes the concrete ode object.

Additional methods are then used to add more information if needed by using the returned object itself, such as initial conditions, and hint and so on before calling the dsolve method on the object.

Here is a very basic setup which include the base ode class and extended to first order two subclasses for now.

restart; 
ODE_factory_class :=module() 
   #notice, normal module. No option object. 
 
   export make_ode:=proc(ode::`=`,func::function(name),$) 
       local dep_variables_found::list,item; 
       local y::symbol; 
       local x::symbol; 
       local ode_order::integer; 
 
       if nops(func)<>1 then 
           error("Parsing error, dependent variable must contain one argument, found ", func); 
       fi; 
       y:=op(0,func); 
       x:=op(1,func); 
       if not has(ode,y) then 
          error ("Supplied ode ",ode," has no ",y); 
       fi; 
 
       if not has(ode,x) then 
          error ("Supplied ode ",ode," has no ",x); 
       fi; 
 
       if not has(ode,func) then 
           error ("Supplied ode ",ode," has no ",func); 
       fi; 
 
       ode_order := PDEtools:-difforder(ode,x); 
 
       #this will check that the dependent variable will show with 
       #SAME argument in the ode. i.e. if y(x) and y(t) show up in same ode, it 
       #will throw exception, which is what we want. 
 
       try 
           dep_variables_found := PDEtools:-Library:-GetDepVars([y],ode); 
       catch: 
           error lastexception; 
       end try; 
 
       #now go over dep_variables_found and check the independent 
       #variable is same as x i.e. ode can be y'(z)+y(z)=0 but function is y(x). 
       for item in dep_variables_found do 
           if not type(item,function) then 
              error("Parsing error. Expected ",func," found ",item," in ode"); 
           else 
              if op(1,item) <> x then 
                  error("Parsing error. Expected ",func," found ",item," in ode"); 
              fi; 
           fi; 
       od; 
 
      #now go over all indents in ode and check that y only shows as y(x) and not as just y 
      #as the PDEtools:-Library:-GetDepVars([_self:-y],ode) code above does not detect this. 
      #i.e. it does not check  y'(x)+y=0 
 
      if numelems(indets(ode,identical(y))) > 0 then 
           error("Parsing error, Can not have ",y," with no argument inside ",ode); 
      fi; 
 
      if ode_order=1 then 
         RETURN(make_first_order_ode(ode,y,x)); 
      elif ode_order=2 then 
         RETURN(make_second_order_ode(ode,y,x)); 
      else 
         RETURN(make_higher_order_ode(ode,y,x)); 
      fi; 
  end proc; 
 
  local make_first_order_ode:=proc(ode::`=`,y::symbol,x::symbol) 
     #decide on what specific type the ode is, and make instant of it 
 
     if first_order_ode_quadrature_class:-is_quadrature(ode,y,x) then 
        RETURN(Object(first_order_ode_quadrature_class,ode,y,x)); 
     elif first_order_ode_linear_class:-is_linear(ode,y,x) then 
        RETURN(Object(first_order_ode_linear_class,ode,y,x)); 
     fi; #and so on 
  end proc; 
 
  local make_second_order_ode:=proc(ode::`=`,y::symbol,x::symbol) 
     #decide on what specific type the ode is, and make instant of it 
     #same as for first order 
  end proc; 
end module; 
#------------------------- 
module solution_class() 
   option object; 
   local the_solution; 
   local is_verified_solution::truefalse:=false; 
   local is_implicit_solution::truefalse:=false; 
 
   export ModuleCopy::static:= proc(_self,proto::solution_class,the_solution::`=`,is_implicit_solution::truefalse,$) 
       _self:-the_solution:=the_solution; 
       _self:-is_implicit_solution:=is_implicit_solution; 
   end proc; 
 
   export get_solution::static:=proc(_self,$) 
        RETURN(_self:-the_solution); 
   end proc; 
 
   export is_verified::static:=proc(_self,$) 
        RETURN(_self:-is_verified_solution); 
   end proc; 
 
   export is_implicit::static:=proc(_self,$) 
        RETURN(_self:-is_implicit_solution); 
   end proc; 
 
   export is_explicit::static:=proc(_self,$) 
        RETURN(not(_self:-is_implicit_solution)); 
   end proc; 
 
   export verify_solution::static:= overload( 
   [ 
      proc(_self, ode::`=`,$) option overload; 
          local stat; 
          stat:= odetest(_self:-the_solution,ode); 
          if stat=0 then 
             _self:-is_verified_solution:=true; 
          else 
            if simplify(stat)=0 then 
                _self:-is_verified_solution:=true; 
            else 
                _self:-is_verified_solution:=false; 
            fi; 
         fi; 
      end, 
 
      proc(_self, ode::`=`,IC::list,$) option overload; 
          local stat; 
          stat:= odetest([_self:-the_solution,IC],ode); 
          if stat=[0,0] then 
             _self:-is_verified_solution:=true; 
          else 
            if simplify(stat)=[0,0] then 
                _self:-is_verified_solution:=true; 
            else 
                _self:-is_verified_solution:=false; 
            fi; 
         fi; 
      end 
   ]); 
end module: 
#------------------------- 
 
module ODE_base_class() 
   option object; 
   local y::symbol; 
   local x::symbol; 
   local func::function(name); #y(x) 
   local ode::`=`; 
   local ode_order::posint; 
   local IC::list:=[]; 
   local parsed_IC::list:=[]; 
   local the_hint::string:=""; 
   local solutions_found::list(solution_class):=[]; 
 
  #exported getters methods 
  export get_ode::static:=proc(_self,$) 
         RETURN(_self:-ode); 
  end proc; 
 
  export get_x::static:=proc(_self,$) 
         RETURN(_self:-x); 
  end proc; 
 
  export get_y::static:=proc(_self,$) 
         RETURN(_self:-y); 
  end proc; 
 
  export get_ode_order::static:=proc(_self,$) 
         RETURN(_self:-ode_order); 
  end proc; 
 
  export get_IC::static:=proc(_self,$) 
         RETURN(_self:-IC); 
  end proc; 
 
  export get_parsed_IC::static:=proc(_self,$) 
         RETURN(_self:-parsed_IC); 
  end proc; 
 
  export get_sol::static:=proc(_self,$) 
         local L:=Array(1..0): 
         local sol; 
         for sol in _self:-solutions_found do 
             L ,= sol:-get_solution(); 
         od; 
         RETURN(convert(L,list)); 
  end proc; 
 
  #exported setters methods 
  export set_hint::static:=proc(_self,hint::string,$) 
         #add code to check if hint is valid 
         _self:-the_hint:=hint; 
  end proc; 
end module; 
 
#------------------------- 
module first_order_ode_quadrature_class() 
  option object(ODE_base_class); 
  local f,g;  #ode of form  y'=f(x)*g(y) 
 
  #this method is not an object method. It is part of the module but does 
  #not have _self. It is called by the factory class to find if the ode 
  #is of this type first 
  export is_quadrature:=proc(ode::`=`,y::symbol,x::symbol)::truefalse; 
         RETURN(true); #for now 
  end proc; 
 
  export ModuleCopy::static:= proc(_self,proto::first_order_ode_quadrature_class,ode::`=`,y::symbol,x::symbol,$) 
      _self:-ode  := ode; 
      _self:-y  := y; 
      _self:-x  := x; 
      _self:-func  := _self:-y(_self:-x); 
      _self:-ode_order :=1; 
  end proc; 
 
  export dsolve::static:=proc(_self,$) 
         local sol,o; 
         #print("Enter first_order_ode_quadrature_class:-dsolve"); 
         #solve the ode for now we will use Maple but in my code 
         #I have my own solver ofcourse. 
         sol:= :-dsolve(_self:-ode,_self:-func); 
         o:=Object(solution_class,sol,false); 
         _self:-solutions_found:= [o]; 
         NULL; 
  end proc; 
end module: 
 
#------------------------- 
module first_order_ode_linear_class() 
  option object(ODE_base_class); 
  local f,g;  #ode of form  y'=f(x)*g(y) 
 
  #this method is not an object method. It is part of the module but does 
  #not have _self. It is called by the factory class to find if the ode 
  #is of this type first 
  export is_linear:=proc(ode::`=`,y::symbol,x::symbol)::truefalse; 
         RETURN(true); #for now 
  end proc; 
 
  export ModuleCopy::static:= proc(_self,proto::first_order_ode_linear_class,ode::`=`,y::symbol,x::symbol,$) 
      _self:-ode  := ode; 
      _self:-y  := y; 
      _self:-x  := x; 
      _self:-func  := _self:-y(_self:-x); 
      _self:-ode_order :=1; 
  end proc; 
 
  export dsolve::static:=proc(_self,$) 
         local sol,o; 
         sol:= :-dsolve(_self:-ode,_self:-func); 
         o:=Object(solution_class,sol,false); 
         _self:-solutions_found[1]:= [o]: 
  end proc: 
end module:
 

Example usage is

o:=ODE_factory_class:-make_ode(diff(y(x),x)=x,y(x)) 
o:-get_ode() 
                           d 
                          --- y(x) = x 
                           dx 
 
o:-dsolve(); 
o:-get_sol() 
                      [       1  2       ] 
                      [y(x) = - x  + c__1] 
                      [       2          ]