Daniel Duffy
C++ author, trainer
- Joined
- 10/4/07
- Messages
- 10,490
- Points
- 648
// An example of a Bridge pattern.
//
// 2022-10-16 DJD #7
// The Bridge structural pattern models abstractions (defined in class hierarchies,
// for example). Each class can have several (volatile) implementations which are also
// usually defined in a class hierarchy). The two class hierarchies should be able to
// vary independently of each other.
// The Bridge pattern is well-known and probably one of the most difficult patterns to
// program in C++98. In the following, we propose a solution using a number of features
// in C++20. Some requirements are:
//
// 1. Efficiency: avoiding subtype polymorphism, i.e. no virtual functions.
// 2, Clean separation of state/data and behaviour between abstractions and implementations
// (good portability)
// 3. Good maintenance a) no monolithic classes, b) reduce proliferation of classes.
//
// To achieve this end, we use the following:
//
// 1. Use of templates and parametric polymorphism (Chaos to Classes 1996, page 31).
// 2. No subtype inheritance, heap objects in classes.
// 3. Static inheritance using CRTP Pattern.
// 4. Template template parameter (to give extra level of indirection).
// 5. Composition rather than inheritance. "Horizontal" delegation.
// 6. Embedded Template Method pattern.
// 7. C++ Concepts to define protocols and provides-requires interfaces.
// 8. Variadic parameters (especially for Strategy).
// 9. The code contains design principles that can be applied to other patterns that
// use a combination of inheritance and composition, for example Strategy, Template Method,
// Visitor, State, Adapter, Decorator(?), Proxy.
// 10. Integration with FP, e.g. std::function<>
//
// Design/Architecture rationale. Four conceptual levels (from blue sky to hardware):
//
// 1. Protocols and interface definitions using C++ Concepts (approach is based on what is
// essentially Architectural Description Language).
// 2. Top-level abstract/device-independent class. It cannot be instantiated as such becase it
// uses CRTP and we must provide a class as a template parameter. The class is called AbstractSensor.
// We can directly inherit from this class (e.g. hard-wired SimpleSensor class ) without introducing
// the full machinery of the Bridge pattern).
// 3. We can now create device-independent derived classes from AbstractSensor. These are domain
// classes with device-independent properties (e.g. AbstractSimulatedSensor) and that delegate to
// hardware classes).
// 4. Hardware/simulated software classes (e.g. FanucSensor, PhilipsSensor) with device-dependent
// properties and that communicate with external systems via drivers.
//
#include <iostream>
#include <string>
// Protocols for sensor implementations, using template template parameter
// An abstract method
template <template <typename T> class Imp, typename T>
concept IStart = requires (Imp<T> d)
{
d.start();
};
// An abstract method
template <template <typename T> class Imp, typename T>
concept IProcess = requires (Imp<T> d)
{
d.process();
};
// An abstract method
template <template <typename T> class Imp, typename T>
concept IStop = requires (Imp<T> d)
{
d.stop();
};
// Essentially, an interface := set of abstract methods
template <template <typename T> class Imp, typename T>
concept ISensorProtocol = IStart<Imp, T> && IProcess<Imp, T> && IStop<Imp, T>;
//
// Hierarchy of "abstract sensors" classes
// Each class can have an implementation
// Bridge pattern: compare it to GUI widget hierarchy in Windows, Linux, DOS, Autocad
template <typename NumericData, typename Derived>
class AbstractSensor
{ // Using CRTP, Template Template Parameter and Template Method Pattern
// This is the compile-time CRTP version of an ABC class with abstract methods.
private:
// Minimal data
public:
AbstractSensor() {}
void start()
{
std::cout << "top level start \n";
static_cast<Derived*>(this)->start();
}
void process()
{ // Implements Template Method Pattern
// do 1...
std::cout << "top level process\n";
static_cast<Derived*>(this)->process();
// do 3...
}
void stop()
{
std::cout << "top level stop \n";
static_cast<Derived*>(this)->stop();
}
void reset()
{ // A kind of template method pattern
std::cout << "*** Resetting ...";
stop();
start();
}
};
template <typename NumericData>
class SimpleSensor : public AbstractSensor<NumericData, SimpleSensor<NumericData>>
{ // Using CRTP, Template Template Parameter and Template Method Pattern
private:
// Minimal data
public:
SimpleSensor() {}
void start()
{
std::cout << "simple start \n";
}
void process()
{ // Implements Template Method Pattern
// do 1...
std::cout << "simple process\n";
// do 3...
}
void stop()
{
std::cout << "simple stop \n";
}
};
template <template <typename T> class Imp,typename T>
requires ISensorProtocol<Imp, T>
class AbstractSimulatedSensor :
public AbstractSensor<T, AbstractSimulatedSensor<Imp, T>>
{
private:
// Device-independent attributes
double _delay;
std::string _name;
Imp<T>& _impl;
public:
AbstractSimulatedSensor(Imp<T>& imp)
: _impl(imp), _delay(0.01), _name(std::string("simulated"))
{}
AbstractSimulatedSensor(Imp<T>& imp, double delay, std::string& name)
: _impl(imp), _delay(delay), _name(name)
{}
void start()
{
std::cout << _name + " slow start\n";
_impl.start();
}
void process()
{ // Implements Template Method Pattern
std::cout << "delay " << _delay << '\'n';
std::cout << " can't you see I'm working on it\n";
_impl.process();
}
void stop()
{
std::cout << _name + " slow stop\n";
_impl.stop();
}
void Switch (Imp<T>& newImpl)
{
_impl = newImpl;
}
double delay() const
{
return _delay;
}
double name() const
{
return _name;
}
};
template <typename T>
class PhilipsSensor
{
private:
public:
PhilipsSensor() {}
void start()
{
std::cout << " Philips start\n";
}
void process()
{
std::cout << " Philips process\n";
}
void stop()
{
std::cout << " Philips stop\n";
}
};
template <typename T>
class FanucSensor
{
private:
public:
FanucSensor() {}
void start()
{
std::cout << " FANUC start\n";
}
void process()
{
std::cout << " FANUC process\n";
}
void stop()
{
std::cout << " FANUC stop\n";
}
};
int main()
{
SimpleSensor<float> sims;
sims.start();
sims.process();
sims.stop();
sims.reset();
using T = double;
T delay = 0.01;
std::string fanucStr("fanuc");
std::string philipsStr("philips");
FanucSensor<T> fSensor;
PhilipsSensor<T> pSensor;
AbstractSimulatedSensor<FanucSensor, T> sensor(fSensor, delay, fanucStr);
AbstractSimulatedSensor<PhilipsSensor, T> sensor2(pSensor, delay, philipsStr);
// Direct to the simulated sensor
//sensor.Switch(pSensor); NOPE, imp is compile-time
sensor.start();
sensor.process();
sensor.stop();
sensor.reset();
sensor2.start();
sensor2.process();
sensor2.stop();
}
A Short History of Computational Finance 1990-2020, a Partial Differential (PDE/FDM) Approach
DANIEL J. DUFFY'S HISTORICAL REVIEW FOCUSES ON THE APPLICABILITY OF PARTIAL DIFFERENTIAL EQUATION (PDE) TECHNIQUES AND RELATED NUMERICAL METHODS, PARTICULARLY FINITE DIFFERENCE METHOD (FDM) THAT ARE APPLIED TO OPTION PRICING AND HEDGING APPLICATIONS.
This article traces the development of computational finance during the period 1990–2020. The views and conclusions are based mainly on the author’s involvement in this area. We focus on the mathematical and numerical models that form a crucial part of this field. Special attention areas are option pricing, partial and stochastic differential equation, and their numerical approximation. We take what we could call a lifecycle approach by tracing the evolution of computational finance, from problem description to design and implementation. The presentation is semi-technical, and it should appeal to a wide range of readers.
1. I have resolved the flaws in heuristic methods for Black as discussed in the article and book.The article is concise in its review of the history of computational finance, a PDE/FDM approach. It points out the limitations of using a heuristic (trial and error) approach to problem-solving, especially PDEs that model derivatives. It also introduces the readers to more recent numerical methods for computing option sensitivities/greeks, showing the importance of numerical methods and their application. Thanks for sharing.
Questions:
- In your opinion, what alternative method (scheme) can handle discontinuous initial conditions (option payoff) since it affects the accuracy of FDM approaches?
- As an expert, I would like your opinion on where we draw the line between a Quant acquiring top-notch programming skills and a full-blown software engineer.
1. I have resolved the flaws in heuristic methods for Black as discussed in the article and book.
2. Can you be more precise? BTW "software engineer" is a meaningless term. Some don't even (know how to) programmer.
Programmer is nicer.
A bit dated, but humorous
Real Programmers Don't Use Pascal - Wikipedia
en.wikipedia.org
Nice thing about definitions is so many to choose from.
Just curious, what level of expertise in programming would you consider sufficient for a Quant?
Too ambiguous.
Can you take a concrete case.
e.g, create a C++ class library for interest rates.
Write a blockchain.
Not sufficient and possibly not necessary: "data structures and algorithms" can mean anything.That's true about definitions. Taking your example of creating a C++ class library for interest rates, I think an excellent knowledge of data structures and algorithms should suffice. In an ever-evolving world of technology, I was just wondering what the bounds were for quants who want to acquire excellent programming skills.
// Test001.cpp
/*
(C) Datasim Education BV 2022
2022-10-20 DJD #8
Super-strippped down sample code to model all kinds of mathematical functions that are neeeded
in numerical applications. Here we reduce the scope by introducing scalar-valued
functions of a scalar argument.
Rationale/preview:
1. Introducing some C++ features to support functional programming style.
2. (mathematically-defined) Vector spaces of scalar, vector and vector-valued functions.
3. Applications of Vector spaces in numerical computing (optimisation, PDE, ML etc. etc.)
(e.g. build complex functions from simpler functions)
4. Heterogeneous container, functions and algorithms (Boost C++ Fusian and Hana).
5. Integration with Design Patterns, e.g. delegation (stateful/stateless versions).
Ideally, this should really in some new Boost C++ library.
** No futher comments for the moment. Later variadic arguments.
*/
#include <functional>
#include <cmath>
#include <iostream>
#include <string>
#include <boost/math/tools/minima.hpp>
template <typename T>
using FType = std::function<T (T)>;
template <typename T>
FType<T> operator + (const FType<T>& f, const FType<T>& g)
{ // Addition
return [=](T x)
{
return f(x) + g(x);
};
}
template <typename T>
FType<T> operator + (const FType<T>& f, T a)
{ // Scalar addition
return [=](T x)
{
return f(x) + a;
};
}
template <typename T>
FType<T> operator * (T a, const FType<T>& f)
{ // Scalar multiplication
return [=](T x)
{
return a*f(x);
};
}
template <typename T>
FType<T> operator - (const FType<T>& f, FType<T>& g)
{ // Subtraction
return [=](T x)
{
return f(x) - g(x);
};
}
template <typename T>
FType<T> operator - (const FType<T>& f)
{ // Unary negation
return [=](T x)
{
return -f(x);
};
}
template <typename T>
FType<T> operator + (const FType<T>& f)
{ // Unary plus
return [=](T x)
{
return +f(x);
};
}
template <typename T>
FType<T> operator << (FType<T>& f, FType<T>& g)
{ // Composition of functions
return [=](T x)
{
return f(g(x));
};
}
template <typename T>
FType<T> assign(T constant)
{ // Create a function from a scalar constant
return [=](T x)
{
return constant;
};
}
template <typename T>
FType<T> exp(const FType<T>& f)
{ // exp
return [=](T x)
{
return std::exp(f(x));
};
}
template <typename T>
FType<T> Exp(const FType<T>& f)
{ // exp
return [=](T x)
{
return std::exp(f(x));
};
}
template <typename T>
FType<T> log(const FType<T>& f)
{ // log
return [=](T x)
{
return std::log(f(x));
};
}
template <typename T>
FType<T> pow(const FType<T>& f, int n)
{ // powers of a function f(x)*...*f(x) (n times)
return [=](T x)
{
return std::pow(f(x), n);
};
}
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// Variadic arguments
//
template <typename RT, typename ...T>
using FTypeV = std::function<RT(T... args)>;
template <typename RT, typename ...T>
FTypeV<RT, T...> operator + (const FTypeV<RT, T...>& f, const FTypeV<RT, T...>& g)
{ // Addition
return [=](T... x)
{
return f(x...) + g(x...);
};
}
// User functions
double G(double x)
{
return x+1;
}
double H(double x, double y)
{
return x + y;
}
double AckleyGlobalFunction(double x)
{
double a = 20.0; double b = 0.2; double c = 2.0*3.14159;
return -a*std::exp(-b*x) - std::exp(std::cos(c*x)) + a + std::exp(1.0);
}
double AckleyGlobalFunctionII(double x)
{ // Building the functions in a HOF assembly process
double a = 20.0; double b = 0.2; double c = 2.0*3.14159;
// Bit unwieldy; used to show function vector spaces.
FType<double> f11 = [&](double x) {return -b*x; };
FType<double> f1 = -a*exp(f11);
FType<double> f22 = [&](double x) {return std::cos(c*x); };
FType<double> f2 = -exp(f22);
// FType<double> f2A = f2 << f1 << f11;
//FType<double> f3 = assign(a + std::exp(1.0));
FType<double> f2B = [&](double x) {return std::exp(1.0) ; };
FType<double> f3 = f2B + a;
FType<double> Ackley = f1 + f2 + f3;
return Ackley(x);
}
double TestFunction(double x)
{ // To show how function algebra works
// Bit unwieldy; used to show function vector spaces.
FType<double> f1 = [](double x) {return x*x; };
FType<double> f2 = [&](double x) {return f1(x)*f1(x); };
FType<double> f3 = [&](double x) {return f2(x)*f2(x); };
FType<double> sumFunc = f1 + f2 + f3;
return sumFunc(x);
}
double RastriginGlobalFunction(double x)
{
double A = 10.0;
return A + x*x - A*std::cos(2.0*3.14159);
}
double SphereGlobalFunction(double x)
{
return x*x;
}
// Testing a function in terms of components
double SphereGlobalFunction(double x, double y, double z)
{
return x * x + y * y + z * z;
}
double SphereX(double x, double y, double z)
{
return x * x;
}
double SphereY(double x, double y, double z)
{
return y * y;
}
double SphereZ(double x, double y, double z)
{
return z * z;
}
int main()
{
FType<double> e = [](double x) { return 5.0; };
FType<double> f = [](double x) { return G(x); };
FType<double> g = [](double x) { return x*x; };
// Currying, I -> H(double x, double y)
FType<double> bindFun = std::bind(H, 1.0, std::placeholders::_1);
auto func1 = g + bindFun;
std::cout << "13, func1? " << func1(3.0) << '\n';
// Currying, II
double y = 1.0;
FType<double> capturdFun = [&](double x) { return H(x, y); };
auto func2 = g + capturdFun;
std::cout << "13, func2? " << func2(3.0) << '\n';
auto h1 = f + g;
FType<double> h2 = f + exp(e);
FType<double> h3 = g + f;
// Experimenting
FType<double> h4 = exp(exp(f));
FType<double> h5 = exp(exp(f) + g);
FType<double> h6 = log(exp(h5) + h5);
FType<double> h7 = h6 + 1.0;
double x = 5.0;
std::cout << "1 " << h1(x) << "\n";
std::cout << "2 " << h2(x) << "\n";
std::cout << "3 " << h3(x) << "\n";
x = 0.0;
std::cout << "4 " << h4(x) << "\n";
std::cout << "5 " << h5(x) << "\n";
std::cout << "6 " << h6(x) << "\n";
std::cout << "7 " << h7(x) << "\n";
{ // Powers
FType<double> f = [](double x) { return x*x; };
int n = 4;
auto prodFun = pow(f, n);
double x = 2.0;
std::cout << "Power: " << prodFun(x) << "\n";
}
{ // Unary minus and plus
auto f = assign(5.0);
auto f1 = exp(f);
auto f2 = +exp(f);
auto f3 = -exp(f);
auto f4 = -(-exp(f));
auto f5 = +(-exp(f));
auto f6 = -(+exp(f));
double x = 5.0;
std::cout << "exp stuff: " << f1(x) << ", " << f2(x) << ", "
<< f3(x) << ", " << f4(x) << ", " << f5(x) << ", " << f6(x) << '\n';
}
FType<double> f11 = [&](double x) {return -b*x; };
FType<double> f1 = -a*exp(f11);
FType<double> f22 = [&](double x) {return std::cos(c*x); };
FType<double> f2 = -exp(f22);
FType<double> f2AB = f2 << f1;
FType<double> f2A = f2AB << f11;
FType<double> f3 = assign(a + std::exp(1.0));
FType<double> Ackley = f1 + f2 + f3;
FType<double> Ackley2 = [](double x) { return AckleyGlobalFunction(x); };
x = -1.0;
while (x < 1.0)
{ // Sanity clause: compute the value in different ways
std::cout << "Error " << x << " " <<
AckleyGlobalFunction(x) - AckleyGlobalFunctionII(x) << " ";
std::cout << "Value " << AckleyGlobalFunction(x) << ","
<< AckleyGlobalFunctionII(x) << ", " << Ackley2(x) << '\n';
x += 0.1;
}
// C. Rastrigin
double A = 10.0;
FType<double> fr1 = assign(A);
FType<double> fr2 = [&](double x) {return x*x; };
FType<double> fr3 = [&](double x) {return std::cos(2.0*3.14159*x); };
FType<double> fr4 = -A*fr3;
FType<double> func = fr1 + fr2 + fr4;
x = -1.0;
while (x < 1.0)
{
std::cout << x << ", " << RastriginGlobalFunction(x) << ", " << func(x) << '\n';
x += 0.1;
}
{ // RT x y z
FTypeV<double, double, double, double> f1 = SphereX;
FTypeV<double, double, double, double> f2 = SphereY;
FTypeV<double, double, double, double> f3 = SphereZ;
FTypeV<double, double, double, double> fSum = f1 + f2 + f3;
std::cout << "Sphere " << fSum(2, 2, 2) << '\n'; // 12
}
}
class SE
{
abstract method doit();
}