Skip to content

Commit 7b4f46c

Browse files
committed
Add correct wrapping for C++-instantiated objects available through global registries, e.g. when loading an XML file. The objects are decorated with the python functions when accessed through the global registries GetObj().
Correct minor issues in PowderPattern plotting and Crystal bond orders. Move global object registries to pyobjcryst.globals. Change the liobjcryst version in conda build config
1 parent ffc3e57 commit 7b4f46c

12 files changed

+226
-28
lines changed

conda-recipe/conda_build_config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ numpy:
88
- 1.11
99

1010
libobjcryst:
11-
- 2017.2.*
11+
- 2021.1.2
1212

1313
boost:
1414
- 1.67

src/extensions/globaloptim_ext.cpp

+14-7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*****************************************************************************/
2020

2121
#include <boost/python/class.hpp>
22+
#include <boost/python/def.hpp>
2223
#include <boost/python/copy_const_reference.hpp>
2324
#include <boost/python/enum.hpp>
2425

@@ -65,28 +66,28 @@ class MonteCarloObjWrap: public MonteCarloObj, public wrapper<MonteCarloObj>
6566

6667
};
6768

68-
void run_optimize(MonteCarloObjWrap& obj, long nbSteps, const bool silent,
69+
void run_optimize(MonteCarloObj& obj, long nbSteps, const bool silent,
6970
const double finalcost, const double maxTime)
7071
{
7172
CaptureStdOut gag;
7273
obj.Optimize(nbSteps, silent, finalcost, maxTime);
7374
}
7475

75-
void multirun_optimize(MonteCarloObjWrap& obj, long nbCycle, long nbSteps,
76+
void multirun_optimize(MonteCarloObj& obj, long nbCycle, long nbSteps,
7677
const bool silent, const double finalcost, const double maxTime)
7778
{
7879
CaptureStdOut gag;
7980
obj.MultiRunOptimize(nbCycle, nbSteps, silent, finalcost, maxTime);
8081
}
8182

82-
void mc_sa(MonteCarloObjWrap& obj, long nbSteps, const bool silent,
83+
void mc_sa(MonteCarloObj& obj, long nbSteps, const bool silent,
8384
const double finalcost, const double maxTime)
8485
{
8586
CaptureStdOut gag;
8687
obj.RunSimulatedAnnealing(nbSteps, silent, finalcost, maxTime);
8788
}
8889

89-
void mc_pt(MonteCarloObjWrap& obj, long nbSteps, const bool silent,
90+
void mc_pt(MonteCarloObj& obj, long nbSteps, const bool silent,
9091
const double finalcost, const double maxTime)
9192
{
9293
CaptureStdOut gag;
@@ -105,6 +106,9 @@ void mc_random_lsq(MonteCarloObjWrap& obj, long nbCycle)
105106

106107
void wrap_globaloptim()
107108
{
109+
// Global object registry
110+
scope().attr("gOptimizationObjRegistry") = boost::cref(gOptimizationObjRegistry);
111+
108112
enum_<AnnealingSchedule>("AnnealingSchedule")
109113
.value("CONSTANT", ANNEALING_CONSTANT)
110114
.value("BOLTZMANN", ANNEALING_BOLTZMANN)
@@ -122,7 +126,10 @@ void wrap_globaloptim()
122126
.value("PARALLEL_TEMPERING_MULTI", GLOBAL_OPTIM_PARALLEL_TEMPERING_MULTI)
123127
;
124128

125-
class_<MonteCarloObjWrap>("MonteCarlo")
129+
class_<MonteCarloObjWrap, boost::noncopyable>("MonteCarlo")
130+
.def(init<>())
131+
.def(init<const MonteCarloObj&>(bp::arg("old")))
132+
.def(init<const std::string&>(bp::arg("name")))
126133
//////////////// OptimizationObj methods
127134
.def("RandomizeStartingConfig", &MonteCarloObj::RandomizeStartingConfig)
128135
.def("Optimize", &run_optimize,
@@ -161,9 +168,9 @@ void wrap_globaloptim()
161168
.def("GetFullRefinableObj", &MonteCarloObj::GetFullRefinableObj,
162169
bp::arg("rebuild")=true,
163170
with_custodian_and_ward_postcall<1,0, return_internal_reference<> >())
164-
.def("GetName", &MonteCarloObj::GetName,
165-
return_value_policy<copy_const_reference>())
171+
.def("GetName", &MonteCarloObj::GetName, return_value_policy<copy_const_reference>())
166172
.def("SetName", &MonteCarloObj::SetName)
173+
.def("GetClassName", &MonteCarloObj::GetClassName)
167174
.def("Print", &MonteCarloObj::Print, &MonteCarloObj::Print)
168175
.def("RestoreBestConfiguration", &MonteCarloObj::RestoreBestConfiguration)
169176
.def("GetNbParamSet", &MonteCarloObj::GetNbParamSet)

src/extensions/objregistry_ext.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ template <class T> bp::object getObjSlice(ObjRegistry<T>& o, bp::slice& s)
5656
return l[s];
5757
}
5858

59+
// Get a MonteCarloObj by dynamic casting. This assumes that the caller (from python)
60+
// first checked the correct type e.g. using GetClassName()
61+
template <class T> MonteCarloObj& getObjCastMonteCarlo(ObjRegistry<T>& reg, const unsigned int i)
62+
{
63+
T* obj = &reg.GetObj(i);
64+
MonteCarloObj *p = dynamic_cast<MonteCarloObj*>(obj);
65+
return *p;
66+
}
67+
68+
// Explicit specialization for ZAtom which is not polymorphic
69+
template <> MonteCarloObj& getObjCastMonteCarlo(ObjRegistry<ZAtom>& reg, const unsigned int i)
70+
{
71+
throw ObjCryst::ObjCrystException("Cannot cast a ZAtom to a MonteCarloObj");
72+
}
5973

6074

6175
/* Wrap all the class methods for the template class */
@@ -118,6 +132,9 @@ wrapClass(class_<ObjRegistry<T> > & c)
118132
// which will effectively invalidate the iterator...
119133
.def("__iter__", range<return_value_policy<reference_existing_object> >
120134
(&ObjRegistry<T>::list_begin, &ObjRegistry<T>::list_end))
135+
// Special casting functions so base class object registries can still
136+
// provide access to the correct python object
137+
.def("_getObjCastMonteCarlo", &getObjCastMonteCarlo<T>, return_internal_reference<>())
121138
;
122139
}
123140

src/extensions/powderpattern_ext.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ void wrap_powderpattern()
316316
.def("GetChi2", &PowderPattern::GetChi2)
317317
.def("GetIntegratedChi2", &PowderPattern::GetIntegratedChi2)
318318
.def("GetLogLikelihood", &PowderPattern::GetLogLikelihood)
319+
.def("Prepare", &PowderPattern::Prepare)
319320
.add_property("mur", &PowderPattern::GetMuR, &PowderPattern::SetMuR)
320321
.add_property("r", &PowderPattern::GetR)
321322
.add_property("r_integrated", &PowderPattern::GetIntegratedR)

src/pyobjcryst/crystal.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,17 @@
3030
"""
3131

3232
__all__ = ["Crystal", "BumpMergePar", "CreateCrystalFromCIF",
33-
"create_crystal_from_cif", "gCrystalRegistry"]
33+
"create_crystal_from_cif"]
3434

3535
import warnings
36+
from types import MethodType
3637
from urllib.request import urlopen
3738
from multiprocessing import current_process
3839
import numpy as np
3940
from pyobjcryst._pyobjcryst import Crystal as Crystal_orig
4041
from pyobjcryst._pyobjcryst import BumpMergePar
4142
from pyobjcryst._pyobjcryst import CreateCrystalFromCIF as CreateCrystalFromCIF_orig
42-
from pyobjcryst._pyobjcryst import gCrystalRegistry
43+
from .refinableobj import wrap_boost_refinableobjregistry
4344

4445
try:
4546
import py3Dmol
@@ -232,7 +233,7 @@ def _display_list(self, xmin=0, xmax=1, ymin=0, ymax=1, zmin=0, zmax=1, enantiom
232233
'symbol': symbol, 'bonds': [], 'bondOrder': []}
233234
for bond in scatt.IterBond():
234235
o = bond.BondOrder
235-
if o == 0:
236+
if o < 1:
236237
o = 1
237238
i1 = bond.GetAtom1().int_ptr()
238239
i2 = bond.GetAtom2().int_ptr()
@@ -630,5 +631,17 @@ def create_crystal_from_cif(file, oneScatteringPowerPerElement=False,
630631
return c
631632

632633

634+
def wrap_boost_crystal(c: Crystal):
635+
"""
636+
This function is used to wrap a C++ Object by adding the python methods to it.
637+
638+
:param c: the C++ created object to which the python function must be added.
639+
"""
640+
if '_display_cif' not in dir(c):
641+
for func in ['CIFOutput', 'UpdateDisplay', 'disable_display_update', 'enable_display_update',
642+
'_display_cif', '_display_list', 'display_3d', 'widget_3d', '_widget_update',
643+
'_widget_on_change_parameter']:
644+
exec("c.%s = MethodType(Crystal.%s, c)" % (func, func))
645+
633646
# PEP8, functions should be lowercase
634647
CreateCrystalFromCIF = create_crystal_from_cif

src/pyobjcryst/globaloptim.py

+67-6
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,46 @@
2121
__all__ = ["MonteCarlo", "AnnealingSchedule", "GlobalOptimType"]
2222

2323
import warnings
24+
from types import MethodType
25+
2426
try:
2527
import ipywidgets as widgets
2628
except ImportError:
2729
widgets = None
28-
from pyobjcryst._pyobjcryst import MonteCarlo as MonteCarlo_orig, AnnealingSchedule, GlobalOptimType
30+
from pyobjcryst._pyobjcryst import MonteCarlo as MonteCarlo_orig, AnnealingSchedule, \
31+
GlobalOptimType, OptimizationObjRegistry
2932
from .refinableobj import *
3033

3134

3235
class MonteCarlo(MonteCarlo_orig):
3336

3437
def Optimize(self, nb_step: int, final_cost=0, max_time=-1):
3538
self._fix_parameters_for_global_optim()
36-
super().Optimize(int(nb_step), True, final_cost, max_time)
39+
if type(self) == MonteCarlo_orig:
40+
self._Optimize(int(nb_step), True, final_cost, max_time)
41+
else:
42+
super().Optimize(int(nb_step), True, final_cost, max_time)
3743

3844
def MultiRunOptimize(self, nb_run: int, nb_step: int, final_cost=0, max_time=-1):
3945
self._fix_parameters_for_global_optim()
40-
super().MultiRunOptimize(int(nb_run), int(nb_step), True, final_cost, max_time)
46+
if type(self) == MonteCarlo_orig:
47+
self._MultiRunOptimize(int(nb_run), int(nb_step), True, final_cost, max_time)
48+
else:
49+
super().MultiRunOptimize(int(nb_run), int(nb_step), True, final_cost, max_time)
4150

4251
def RunSimulatedAnnealing(self, nb_step: int, final_cost=0, max_time=-1):
4352
self._fix_parameters_for_global_optim()
44-
super().RunSimulatedAnnealing(int(nb_step), True, final_cost, max_time)
53+
if type(self) == MonteCarlo_orig:
54+
self._RunSimulatedAnnealing(int(nb_step), True, final_cost, max_time)
55+
else:
56+
super().RunSimulatedAnnealing(int(nb_step), True, final_cost, max_time)
4557

4658
def RunParallelTempering(self, nb_step: int, final_cost=0, max_time=-1):
4759
self._fix_parameters_for_global_optim()
48-
super().RunParallelTempering(int(nb_step), True, final_cost, max_time)
60+
if type(self) == MonteCarlo_orig:
61+
self._RunParallelTempering(int(nb_step), True, final_cost, max_time)
62+
else:
63+
super().RunParallelTempering(int(nb_step), True, final_cost, max_time)
4964

5065
def _fix_parameters_for_global_optim(self):
5166
# Fix parameters that should not be optimised in a MonterCarlo run
@@ -58,7 +73,7 @@ def _fix_parameters_for_global_optim(self):
5873

5974
def widget(self):
6075
"""
61-
Display a simple widget for this MonteCarloObj, which only updates the current
76+
Display a simple widget for this MonteCarlo, which only updates the current
6277
cost (log-likelihood). Requires ipywidgets
6378
"""
6479
if widgets is None:
@@ -101,3 +116,49 @@ def _widget_update(self):
101116
else:
102117
self._widget_llk.value = "LLK=%12.2f " % self.llk
103118
self._widget_llk.layout.width = '%dem' % len(self._widget_llk.value)
119+
120+
121+
def wrap_boost_montecarlo(c: MonteCarlo):
122+
"""
123+
This function is used to wrap a C++ Object by adding the python methods to it.
124+
125+
:param c: the C++ created object to which the python function must be added.
126+
"""
127+
if 'widget' not in dir(c):
128+
for func in ['Optimize', 'MultiRunOptimize', 'RunSimulatedAnnealing', 'RunParallelTempering']:
129+
# We keep access to the original functions... Yes, it's a kludge...
130+
exec("c._%s = c.%s" % (func, func))
131+
for func in ['Optimize', 'MultiRunOptimize', 'RunSimulatedAnnealing', 'RunParallelTempering',
132+
'_fix_parameters_for_global_optim', 'widget', 'UpdateDisplay',
133+
'disable_display_update', 'enable_display_update', '_widget_update']:
134+
exec("c.%s = MethodType(MonteCarlo.%s, c)" % (func, func))
135+
136+
137+
class OptimizationObjRegistryWrapper(OptimizationObjRegistry):
138+
"""
139+
Wrapper class with a GetObj() method which can correctly wrap C++ objects with
140+
the python methods. This is only needed when the objects have been created
141+
from C++, e.g. when loading an XML file.
142+
"""
143+
144+
def GetObj(self, i):
145+
o = self._GetObj(i)
146+
# TODO
147+
print("Casting OptimizationObj to MonteCarlo and wrapping..")
148+
# We get the object as an OptimizationObj, which prevents access to some functions
149+
# So we use this function to cast to a MonteCarloObj
150+
o = self._getObjCastMonteCarlo(i)
151+
wrap_boost_montecarlo(o)
152+
return o
153+
154+
155+
def wrap_boost_optimizationobjregistry(o):
156+
"""
157+
This function is used to wrap a C++ Object by adding the python methods to it.
158+
159+
:param c: the C++ created object to which the python function must be added.
160+
"""
161+
# TODO: moving the original function is not very pretty. Is there a better way ?
162+
if '_GetObj' not in dir(o):
163+
o._GetObj = o.GetObj
164+
o.GetObj = MethodType(OptimizationObjRegistryWrapper.GetObj, o)

src/pyobjcryst/globals.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env python
2+
##############################################################################
3+
#
4+
# globals
5+
#
6+
# File coded by: Vincent Favre-Nicolin
7+
#
8+
# See AUTHORS.txt for a list of people who contributed.
9+
# See LICENSE.txt for license information.
10+
#
11+
##############################################################################
12+
13+
""" Global objects are exposed here. These are the main objects registries,
14+
which are tweaked to wrap pure C++ objects with the python methods.
15+
"""
16+
17+
__all__ = ["gCrystalRegistry","gPowderPatternRegistry", "gRefinableObjRegistry", "gScattererRegistry",
18+
"gOptimizationObjRegistry", "gTopRefinableObjRegistry"]
19+
20+
from .refinableobj import wrap_boost_refinableobjregistry
21+
from .globaloptim import wrap_boost_optimizationobjregistry
22+
from pyobjcryst._pyobjcryst import gCrystalRegistry
23+
from pyobjcryst._pyobjcryst import gOptimizationObjRegistry
24+
from pyobjcryst._pyobjcryst import gPowderPatternRegistry
25+
from pyobjcryst._pyobjcryst import gRefinableObjRegistry
26+
from pyobjcryst._pyobjcryst import gScattererRegistry
27+
from pyobjcryst._pyobjcryst import gTopRefinableObjRegistry
28+
29+
# Wrap registries with python methods
30+
wrap_boost_refinableobjregistry(gCrystalRegistry)
31+
wrap_boost_refinableobjregistry(gPowderPatternRegistry)
32+
wrap_boost_refinableobjregistry(gRefinableObjRegistry)
33+
wrap_boost_refinableobjregistry(gTopRefinableObjRegistry)
34+
wrap_boost_optimizationobjregistry(gOptimizationObjRegistry)

src/pyobjcryst/io.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@
3131
import gzip
3232
import os
3333
from pyobjcryst._pyobjcryst import XMLCrystTag, \
34-
XMLCrystFileLoadAllObject as XMLCrystFileLoadAllObject_orig,\
34+
XMLCrystFileLoadAllObject as XMLCrystFileLoadAllObject_orig, \
3535
XMLCrystFileSaveGlobal as XMLCrystFileSaveGlobal_orig
36-
37-
from pyobjcryst._pyobjcryst import gTopRefinableObjRegistry
36+
from .globals import gTopRefinableObjRegistry
3837

3938

4039
def xml_cryst_file_load_all_object(file, verbose=False):

0 commit comments

Comments
 (0)