amp-0.6/0002755000175000017500000000000013137634472012030 5ustar muammarmuammaramp-0.6/setup.py0000644000175000017500000000116513137634440013536 0ustar muammarmuammar#!/usr/bin/env from setuptools import setup setup( name = 'amp', version = 'dev', long_description = open('README.md').read(), packages = ['amp', 'amp.descriptor', 'amp.regression', 'amp.model'], package_dir = {'amp': 'amp', 'descriptor' : 'descriptor', 'regression' : 'regression', 'model' : 'model',}, classifiers = [ 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', ], install_requires=['numpy>=1.7.0', 'matplotlib', 'ase']) amp-0.6/tests/0002755000175000017500000000000013142667007013166 5ustar muammarmuammaramp-0.6/tests/test_NN_nodeplot.py0000644000175000017500000000546013137634440017020 0ustar muammarmuammarimport matplotlib matplotlib.use('Agg') # For headless operation. from ase.calculators.emt import EMT from ase.lattice.surface import fcc110 from ase import Atoms, Atom from ase.md.velocitydistribution import MaxwellBoltzmannDistribution from ase import units from ase.md import VelocityVerlet from ase.constraints import FixAtoms from amp import Amp from amp.descriptor.gaussian import Gaussian from amp.model.neuralnetwork import NeuralNetwork from amp.model import LossFunction from amp.model.neuralnetwork import NodePlot from amp.utilities import hash_images def generate_data(count): """Generates test or training data with a simple MD simulation.""" atoms = fcc110('Pt', (2, 2, 2), vacuum=7.) adsorbate = Atoms([Atom('Cu', atoms[7].position + (0., 0., 2.5)), Atom('Cu', atoms[7].position + (0., 0., 5.))]) atoms.extend(adsorbate) atoms.set_constraint(FixAtoms(indices=[0, 2])) atoms.set_calculator(EMT()) MaxwellBoltzmannDistribution(atoms, 300. * units.kB) dyn = VelocityVerlet(atoms, dt=1. * units.fs) newatoms = atoms.copy() newatoms.set_calculator(EMT()) newatoms.get_potential_energy() images = [newatoms] for step in range(count - 1): dyn.run(50) newatoms = atoms.copy() newatoms.set_calculator(EMT()) newatoms.get_potential_energy() images.append(newatoms) return images def train_data(images, setup_only=False): label = 'nodeplot_test/calc' train_images = images calc = Amp(descriptor=Gaussian(), model=NeuralNetwork(hiddenlayers=(5, 5)), label=label, cores=1) loss = LossFunction(convergence={'energy_rmse': 0.02, 'force_rmse': 0.02}) calc.model.lossfunction = loss if not setup_only: calc.train(images=train_images, ) for image in train_images: print ("energy =", calc.get_potential_energy(image)) print ("forces =", calc.get_forces(image)) else: images = hash_images(train_images) calc.descriptor.calculate_fingerprints(images=images, log=calc._log, parallel={'cores': 1}, calculate_derivatives=False) calc.model.fit(trainingimages=images, descriptor=calc.descriptor, log=calc._log, parallel={'cores': 1}, only_setup=True) return calc def test_nodeplot(): """Nodeplot creation test.""" images = generate_data(2) calc = train_data(images, setup_only=True) nodeplot = NodePlot(calc) nodeplot.plot(images, filename='nodeplottest.pdf') if __name__ == "__main__": test_nodeplot() amp-0.6/tests/consistency_test/0002755000175000017500000000000013142667007016566 5ustar muammarmuammaramp-0.6/tests/consistency_test/gaussian_neural_test/0002755000175000017500000000000013142667007023005 5ustar muammarmuammaramp-0.6/tests/consistency_test/gaussian_neural_test/train_test.py0000644000175000017500000010554113137634440025536 0ustar muammarmuammar""" This script creates a list of three images. It then calculates behler-neural scheme cost function, energy per atom RMSE and force RMSE of different combinations of images with and without fortran modules on different number of cores, and check consistency between them. """ import numpy as np from ase import Atoms from ase.calculators.emt import EMT from amp import Amp from amp.descriptor.gaussian import Gaussian from amp.model.neuralnetwork import NeuralNetwork from amp.model import LossFunction from amp.regression import Regressor # Making the list of images def make_images(): """Makes test images.""" images = [Atoms(symbols='Pd3O2', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[7.78, 0., 0.], [0., 5.50129076, 0.], [0., 0., 15.37532269]]), positions=np.array( [[3.89, 0., 8.37532269], [0., 2.75064538, 8.37532269], [3.89, 2.75064538, 8.37532269], [5.835, 1.37532269, 8.5], [5.835, 7.12596807, 8.]])), Atoms(symbols='Pd3O2', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[7.78, 0., 0.], [0., 5.50129076, 0.], [0., 0., 15.37532269]]), positions=np.array( [[3.88430768e+00, 5.28005966e-03, 8.36678641e+00], [-1.01122240e-02, 2.74577426e+00, 8.37861758e+00], [3.88251383e+00, 2.74138906e+00, 8.37087611e+00], [5.82067191e+00, 1.19156898e+00, 8.97714483e+00], [5.83355445e+00, 7.53318593e+00, 8.50142020e+00]])), Atoms(symbols='Pd3O2', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[7.78, 0., 0.], [0., 5.50129076, 0.], [0., 0., 15.37532269]]), positions=np.array( [[3.87691266e+00, 9.29708987e-03, 8.35604207e+00], [-1.29700138e-02, 2.74373753e+00, 8.37941484e+00], [3.86813484e+00, 2.73488653e+00, 8.36395999e+00], [5.80386111e+00, 7.98192190e-01, 9.74324179e+00], [5.83223956e+00, 8.23855393e+00, 9.18295137e+00]]))] for atoms in images: atoms.set_calculator(EMT()) atoms.get_potential_energy(apply_constraint=False) atoms.get_forces(apply_constraint=False) return images # Parameters cutoff = 5.5 activation = 'sigmoid' hiddenlayers = {'O': (5, 5), 'Pd': (5, 5)} Gs = {'O': [{'eta': 0.05, 'type': 'G2', 'element': 'O'}, {'eta': 0.05, 'type': 'G2', 'element': 'Pd'}, {'eta': 2.0, 'type': 'G2', 'element': 'O'}, {'eta': 2.0, 'type': 'G2', 'element': 'Pd'}, {'eta': 4.0, 'type': 'G2', 'element': 'O'}, {'eta': 4.0, 'type': 'G2', 'element': 'Pd'}, {'eta': 8.0, 'type': 'G2', 'element': 'O'}, {"eta": 8.0, "type": "G2", "element": "Pd"}, {"eta": 20.0, "type": "G2", "element": "O"}, {"eta": 20.0, "type": "G2", "element": "Pd"}, {"eta": 40.0, "type": "G2", "element": "O"}, {"eta": 40.0, "type": "G2", "element": "Pd"}, {"eta": 80.0, "type": "G2", "element": "O"}, {"eta": 80.0, "type": "G2", "element": "Pd"}, {"zeta": 1.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}], "Pd": [{"eta": 0.05, "type": "G2", "element": "O"}, {"eta": 0.05, "type": "G2", "element": "Pd"}, {"eta": 2.0, "type": "G2", "element": "O"}, {"eta": 2.0, "type": "G2", "element": "Pd"}, {"eta": 4.0, "type": "G2", "element": "O"}, {"eta": 4.0, "type": "G2", "element": "Pd"}, {"eta": 8.0, "type": "G2", "element": "O"}, {"eta": 8.0, "type": "G2", "element": "Pd"}, {"eta": 20.0, "type": "G2", "element": "O"}, {"eta": 20.0, "type": "G2", "element": "Pd"}, {"eta": 40.0, "type": "G2", "element": "O"}, {"eta": 40.0, "type": "G2", "element": "Pd"}, {"eta": 80.0, "type": "G2", "element": "O"}, {"eta": 80.0, "type": "G2", "element": "Pd"}, {"zeta": 1.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}]} fingerprints_range = {"O": np.array([[0.9576297744816821, 0.9781635537721822], [1.5965219624007356, 2.029991552829194], [0.6490867939769083, 0.9554902891920712], [0.8835113617162015, 1.385491645778086], [0.45757754941615236, 0.9354370590550468], [0.5038098795235629, 0.9704216656880006], [0.24776862922895004, 0.8975120854112218], [0.1778411370477522, 0.5033369040695346], [0.04756795975972335, 0.7929693614866126], [0.010678411893107285, 0.08274205794992942], [0.0032126590280589355, 0.6450875782155161], [0.00014810311804588016, 0.004685139865518786], [1.4684356130451328e-05, 0.42691677760826546], [3.687931521390313e-08, 1.5871730901594423e-05], [0.03019875732248386, 0.20698709522424386], [3.363261257762341, 5.363914410336563], [2.262959763053792, 3.99309008637724], [0.43931018496430263, 0.6100947886487761], [3.048864359631384, 3.906027779282084], [2.4759824537761026, 4.32175703694348], [0.0014242919510283594, 0.0662909452084825], [2.1248328227888496, 4.238756592436429], [1.4573759593003581, 2.815733155339926], [0.2986140349485413, 0.5813203232773206], [1.746995212759835, 2.3899513815298894], [1.5256310348626507, 3.1071875821187103], [3.1682461497983404e-06, 0.006799486167343933], [1.1510284000759108, 3.1879752591881956], [0.8406198864990045, 1.7423917588951667], [0.1379710925632159, 0.5277787358337935], [0.6114872725198919, 1.340305590989966], [0.7164066603703434, 2.0891604176934395]]), "Pd": np.array([[1.154607323053423, 1.3305487045981295], [0.551759102399155, 1.3713637661332694], [0.6469877982543076, 0.9038561263894056], [0.1836383922219796, 0.7510644180086835], [0.3706404542226067, 0.6308010197966096], [0.061908829417001224, 0.42094513726712196], [0.13355181104922664, 0.3205532867729253], [0.0076308937151500725, 0.14249172183309694], [0.009302322319625642, 0.048059061272533475], [1.785148490055681e-05, 0.006770671357855657], [0.00014511169324163886, 0.0025160536892603904], [8.04287954299944e-10, 4.599501555987974e-05], [3.686778281930424e-08, 8.524742995946123e-06], [1.6422373031638726e-18, 2.1846879428444836e-09], [1.7383991321368732, 2.9347075860454988], [4.544797763122505, 7.28152322128857], [0.979596152457089, 1.9620028955131916], [0.49196325681052067, 1.0767938436994269], [3.139828490686222, 4.524661360317331], [1.23826020263128, 2.4316125030019147], [1.4012907765627602, 2.476988108604277], [3.4081270882827277, 6.0197611413153655], [0.4897744167832538, 1.6851590166945567], [0.15650247391327193, 0.6306691375027177], [2.2224602701293357, 3.262899280344123], [0.9717045670826352, 1.815787153995498], [0.9659634528285561, 2.163669429908823], [2.3741505301983685, 4.8131425542358715], [0.12244182821265122, 1.357426673243403], [0.0326810274613218, 0.22677866750542988], [1.4047756390099746, 2.37308600253783], [0.8248597322302271, 1.3563088031163417]])} weights = {"O": {1: np.matrix([[0.00036942165470836563, 0.022852891328080965, -0.007763989520407272, 0.0017904084613678817, -0.04680830976127531], [-0.011220619170475267, -0.031465481900468384, -0.0185587250581268, 0.00029876794872765733, -0.03270538302430687], [0.009269384571647388, -0.004439491584362951, 0.02041622613407708, -0.04075220241750707, -0.004384443250886716], [-0.02045226889653854, -0.04085271674587569, -0.0007470148939682439, 0.017448937916376722, -0.03247360282480993], [0.014076539841285075, -0.0006001148651055555, -0.011206188631385075, 0.036131770356541804, 0.04019195568663911], [0.04438555375359607, -0.03630318854778723, - 0.011787189723001579, 0.03403384156560013, 0.015653363757362176], [0.02134038436971622, 0.000554719592425923, -0.04353602059838731, 0.02829112575071807, -0.010315738192632054], [-0.009864186941597866, 0.025867111325423034, -0.030222981254973712, -0.009255262808615411, -0.0047940678082599025], [0.009775595855839286, -0.004398102065676125, -0.00195136837351699, -0.0015883410280669308, 0.03528054083271703], [0.0088457892425432, -0.0017778202887624855, -0.030182606288789264, 0.03153096931177092, -0.02709527292127762], [-0.02911935923819357, -0.011844856703362105, 0.03358589444051113, 0.007149386960731488, -0.007590532737964187], [-0.03944400124516653, 0.03562647918045643, -0.041584201666104756, -0.03482985747462908, -0.045374395214468496], [0.019057890033725933, -0.012580031773554046, 0.04290707878850142, 0.04177600131985214, -0.03500384259370384], [-0.02033084113684249, -0.01111345650394805, -0.005485434669132497, 0.03554246348547074, 0.031421467582530324], [-0.03310168568774624, 0.04617363212275834, 0.03868456178440169, 0.012151585419746959, -0.007359447003748548], [0.044255356329426065, 0.036122120043098505, 0.001842950538131695, -0.01615761183192349, -0.03771427943410247], [-0.0381118034639101, -0.04643318160382238, 0.02900519652241655, -0.008475138348622263, 0.021893066028991853], [0.016038314042298385, 0.03545540262812917, -0.031220884269865096, -0.033670838618425646, 0.04684810506588663], [0.037697271308168634, -0.04250612661317486, 0.0028173761257807364, 0.04503591051281573, -0.005888259820159045], [-0.01688674535740177, 0.03765441774468983, 0.040162723331136185, 0.023291060425779497, 0.01875483057892434], [0.009559969717541077, -0.010986361005406543, 0.017717618257908102, 0.021594452542040676, 0.00668490554203105], [0.02899572788647327, 0.03884753546552183, 0.0334345646492913, -0.0009724588802520473, 0.008901825903656319], [0.04472782971579241, 0.020125743534124996, 0.018466622131502394, 0.014248370483492187, 0.02954224116911444], [0.018038582886592464, 0.007882237781735343, -0.005639481686277245, -0.030317048204748388, 0.011443284253329196], [-0.014574589075944028, 0.027312879897418138, -0.0052516221359138054, -0.02858166510190807, -0.0218508786228111], [-0.019062166466149163, -0.0421343628780219, -0.0292511219030615, -0.04063165343284807, -0.026551753085291934], [-0.006973189792228912, 0.018725587327618767, 0.037936857142053707, 0.011375377365914208, -0.03823975980860963], [-0.03087795180506949, -0.002166181826768615, -0.009411940441267343, 0.008062289773496219, 0.03143133615872179], [0.022767389458292583, -0.032719990839286985, 0.010234126834754581, -0.0025988597425086815, 0.012893424785935387], [0.03729503214821439, -0.04055234881977389, -0.033180455803208164, -0.003962067731434399, -0.04089277483943934], [-0.005215540749534078, 0.013163002568367034, 0.03980552568163612, 0.00803385354609431, 7.658166702390057e-05], [0.013936695364567375, 0.017657437899754047, 0.027548202328624413, -0.0008692880197060243, 0.032762776542753225], [0.0, 0.0, 0.0, 0.0, 0.0]]), 2: np.matrix([[-0.13315704861614908, 0.21616481887190436, 0.07102546888848049, -0.2758348650926486, -0.12434812671933795], [-0.024314957289090222, -0.16392515185308187, 0.2058922926890992, 0.2154935160814611, 0.11014812360618259], [-0.08133895309316427, -0.1937923029504461, 0.206977413616443, 0.03575405386811248, -0.10559013113242327], [0.1469937256217183, 0.07621742865022896, 0.08882575726900893, -0.2577928927812111, 0.2670748892517893], [-0.141370342762172, -0.23738939477247786, -0.06633785630500305, -0.24779722808875726, 0.17677488447247947], [0.0, 0.0, 0.0, 0.0, 0.0]]), 3: np.matrix([[0.0377280558085506], [0.013842778159018243], [-0.29408570195900635], [0.19529036441834974], [-0.16745509851929688], [0.0]])}, "Pd": {1: np.matrix([[0.008070279669324429, -0.04006333630883027, -0.04312742429320118, -0.03942171198922403, -0.04540662900302544], [0.01339814716182161, -0.022961503636403632, -0.006969046155031772, 0.01539617792272549, -0.02587844848147742], [0.045033334892680674, 0.0034430687137840393, 0.02405223418836909, -0.035506042140031155, 0.021328894351546446], [-0.04416667286322164, -0.03993519399665675, 0.032311654583997304, -0.03745738975064494, 0.006061355326268905], [-0.043438846516273555, 0.020424466564239616, -0.03712722505187403, -0.04417848105963802, -0.008777813735156417], [0.03965347387678732, -0.01799472378269024, 0.0362866746012956, 0.009704740166992, 0.0004118619760827419], [-0.03180969154106336, -0.006918591959222585, 0.014099398062742227, -0.022931651589221756, 0.03148626725702887], [0.04573128229126357, 0.016654751576744925, -0.028910689496630722, 0.02242435838167882, -0.02783084152657823], [0.030147617474449384, -0.009580788002314114, 0.026913224902892594, -0.006350898911528513, -0.01580260272955647], [-0.03128280563473111, -0.044359797916295726, -0.0455871021838766, -0.022323871191166217, -0.025520059574284607], [-0.004213681746207731, -0.027963910926939888, -0.03734025976436221, -0.029904058599404374, -0.023362113055890702], [0.03140805808988659, -0.01625862158802977, -0.012926251592534549, 0.0199950518624378, 0.00017000814436556738], [0.03611338398893238, -0.04064588225668243, -0.03548786885528668, -0.034119876099748085, -0.03249791207428783], [0.04302813264222295, -0.031784410672976354, -0.0018505347572984332, -0.02619493567773821, -0.009963146880811465], [-0.00382761556441661, 0.02051612655974898, -0.015084868592703193, 0.036644660445905974, 0.024267396930057042], [0.0027419126458524262, -0.01875730493117643, 0.042029556463568374, -0.033491496522005004, 0.04664358315093048], [-0.00857053904710025, 0.004386575075249165, -0.03681921382606547, 0.024055769666913862, -0.006710822409842235], [0.01600071354805395, -0.03619212782962617, -0.007657861036073181, 0.04579883161005442, -0.027272703382017247], [0.024782613292205463, 0.02454697361926271, 0.014219326292126383, -0.03120859763819632, 0.019746899921596867], [-0.008107835898640163, -0.02411112524744128, 0.01680784294783398, -0.03942450668164303, -0.02148968897141828], [0.006160769106771449, -0.02608742029162942, -0.03445574192255718, 0.011100495475242236, -0.011890887277678633], [0.019265102424069563, -0.019510992393145597, -0.039330197040643305, 0.028930252847621296, 0.04535579375056527], [0.0003841258275426168, -0.03140536534416318, 0.004402540856303851, -0.006596225898408456, -0.012287524451218383], [0.032434589752896065, -0.038422865723774166, 0.04121673691259908, 0.026471126594987765, -0.045659510547159485], [0.016693221128737612, 0.033475787637348264, -0.01216104367054778, -0.04682497168901334, -0.025748662607038442], [-0.030035984906774393, 0.03528987279339724, 0.01842649225978525, 0.013967345908646303, 0.030368471307811548], [-0.004245382943207754, 0.004346310546406856, 0.04395403376516939, -0.03528225866346128, 0.040526584371759225], [-0.026240373867317947, -0.02790624801091845, 0.033248579584558235, -0.03456761843589754, -0.00921953855906435], [-0.04029772119462781, 0.03944849938380114, 0.03367466933743388, -0.04654081205741553, -0.02559442696348037], [-0.019162242379646047, -0.0074198239538341496, -0.03481645962457279, 0.0023221563528588313, -0.01362951107641086], [-0.04359327067093935, 0.008182459343197494, -0.004311982184810589, 0.013459029430653538, -0.02593952116632298], [0.03419829018664716, -0.02909906291417496, 0.0450381809975251, 0.04636855435694584, 0.004474211596899327], [0.0, 0.0, 0.0, 0.0, 0.0]]), 2: np.matrix([[0.07339646055942084, -0.22682470946032204, -0.07464451676678477, -0.21765816530655968, 0.10447399748556846], [-0.07339330664074986, -0.2620525555813218, -0.010761218306725495, 0.07390075065002266, 0.11039186125577433], [-0.17516748044584285, -0.2837828871933906, -0.02085650668287642, 0.08755824083276131, 0.07220039658405131], [0.23974597425595473, 0.24760019759492297, -0.22060915253115443, -0.28310518337421325, -0.016857214958102662], [0.11687787432599622, -0.10151689213238121, 0.18735099239621017, 0.21356695418645139, -0.240568272158666], [0.0, 0.0, 0.0, 0.0, 0.0]]), 3: np.matrix([[0.05906457619187622], [-0.29300196568707426], [-0.018802515167880285], [-0.2723126668305828], [0.22668984898833738], [0.0]])}} scalings = {"O": {"intercept": 4.2468934359280288, "slope": 3.1965614888424687}, "Pd": {"intercept": 4.2468934359280288, "slope": 3.1965614888424687}} # Testing pure-python and fortran versions of Gaussian-Neural on different # number of processes and different number of images def test(): """Guassian/Neural training. Checks consistency of pure-python and fortran versions. """ images = make_images() convergence = {'energy_rmse': 10.**10., 'energy_maxresid': 10.**10., 'force_rmse': 10.**10., 'force_maxresid': 10.**10., } regressor = Regressor(optimizer='BFGS') count = 0 for fortran in [False, True]: for cores in range(1, 2): string = 'consistgauss/%s-%i' label = string % (fortran, cores) calc = Amp(descriptor=Gaussian(cutoff=cutoff, Gs=Gs, fortran=fortran,), model=NeuralNetwork(hiddenlayers=hiddenlayers, weights=weights, scalings=scalings, activation=activation, fprange=fingerprints_range, regressor=regressor,), label=label, cores=1) lossfunction = LossFunction(convergence=convergence) calc.model.lossfunction = lossfunction calc.train(images=images,) if count == 0: ref_loss = calc.model.lossfunction.loss ref_energy_loss = calc.model.lossfunction.energy_loss ref_force_loss = calc.model.lossfunction.force_loss ref_dloss_dparameters = \ calc.model.lossfunction.dloss_dparameters else: assert (abs(calc.model.lossfunction.loss - ref_loss) < 10.**(-10.)), \ '''Loss function value for %r fortran, and %i cores is not consistent with the value of python version on single core.''' % (fortran, cores) assert (abs(calc.model.lossfunction.energy_loss - ref_energy_loss) < 10.**(-9.)), \ '''Energy rmse value for %r fortran, and %i cores is not consistent with the value of python version on single core.''' % (fortran, cores) assert (abs(calc.model.lossfunction.force_loss - ref_force_loss) < 10.**(-9.)), \ '''Force rmse value for %r fortran, and %i cores is not consistent with the value of python version on single core.''' % (fortran, cores) for _ in range(len(ref_dloss_dparameters)): assert (calc.model.lossfunction.dloss_dparameters[_] - ref_dloss_dparameters[_] < 10.**(-10.)) '''Derivative of the cost function for %r fortran, and %i cores is not consistent with the value of python version on single core. ''' % (fortran, cores) count = count + 1 if __name__ == '__main__': test() amp-0.6/tests/consistency_test/gaussian_neural_test/force_call_test.py0000644000175000017500000010056413137634440026512 0ustar muammarmuammar""" This script creates a list of three images. It then calculates behler-neural scheme forces and energies of different combinations of them with and without fortran modules, and check consistency between them. """ import numpy as np from ase import Atoms from amp import Amp from amp.descriptor.gaussian import Gaussian from amp.model.neuralnetwork import NeuralNetwork def make_images(): """Makes test images.""" images = [Atoms(symbols='Pd4O2', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[7.78, 0., 0.], [0., 5.50129076, 0.], [0., 0., 15.37532269]]), positions=np.array( [[0., 0., 8.37532269], [3.89, 0., 8.37532269], [0., 2.75064538, 8.37532269], [3.89, 2.75064538, 8.37532269], [5.835, 1.37532269, 8.5], [5.835, 7.12596807, 8.]])), Atoms(symbols='Pd4O2', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[7.78, 0., 0.], [0., 5.50129076, 0.], [0., 0., 15.37532269]]), positions=np.array( [[8.43409903e-03, -4.40430259e-03, 8.37107726e+00], [3.88430768e+00, 5.28005966e-03, 8.36678641e+00], [-1.01122240e-02, 2.74577426e+00, 8.37861758e+00], [3.88251383e+00, 2.74138906e+00, 8.37087611e+00], [5.82067191e+00, 1.19156898e+00, 8.97714483e+00], [5.83355445e+00, 7.53318593e+00, 8.50142020e+00]])), Atoms(symbols='Pd4O2', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[7.78, 0., 0.], [0., 5.50129076, 0.], [0., 0., 15.37532269]]), positions=np.array( [[1.84507759e-02, -9.96620379e-03, 8.36465110e+00], [3.87691266e+00, 9.29708987e-03, 8.35604207e+00], [-1.29700138e-02, 2.74373753e+00, 8.37941484e+00], [3.86813484e+00, 2.73488653e+00, 8.36395999e+00], [5.80386111e+00, 7.98192190e-01, 9.74324179e+00], [5.83223956e+00, 8.23855393e+00, 9.18295137e+00]]))] return images # # Parameters cutoff = 6.5 activation = 'tanh' hiddenlayers = {'O': (5, 5), 'Pd': (5, 5)} Gs = {'O': [{'eta': 0.05, 'type': 'G2', 'element': 'O'}, {'eta': 0.05, 'type': 'G2', 'element': 'Pd'}, {'eta': 2.0, 'type': 'G2', 'element': 'O'}, {'eta': 2.0, 'type': 'G2', 'element': 'Pd'}, {'eta': 4.0, 'type': 'G2', 'element': 'O'}, {'eta': 4.0, 'type': 'G2', 'element': 'Pd'}, {'eta': 8.0, 'type': 'G2', 'element': 'O'}, {"eta": 8.0, "type": "G2", "element": "Pd"}, {"eta": 20.0, "type": "G2", "element": "O"}, {"eta": 20.0, "type": "G2", "element": "Pd"}, {"eta": 40.0, "type": "G2", "element": "O"}, {"eta": 40.0, "type": "G2", "element": "Pd"}, {"eta": 80.0, "type": "G2", "element": "O"}, {"eta": 80.0, "type": "G2", "element": "Pd"}, {"zeta": 1.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}], "Pd": [{"eta": 0.05, "type": "G2", "element": "O"}, {"eta": 0.05, "type": "G2", "element": "Pd"}, {"eta": 2.0, "type": "G2", "element": "O"}, {"eta": 2.0, "type": "G2", "element": "Pd"}, {"eta": 4.0, "type": "G2", "element": "O"}, {"eta": 4.0, "type": "G2", "element": "Pd"}, {"eta": 8.0, "type": "G2", "element": "O"}, {"eta": 8.0, "type": "G2", "element": "Pd"}, {"eta": 20.0, "type": "G2", "element": "O"}, {"eta": 20.0, "type": "G2", "element": "Pd"}, {"eta": 40.0, "type": "G2", "element": "O"}, {"eta": 40.0, "type": "G2", "element": "Pd"}, {"eta": 80.0, "type": "G2", "element": "O"}, {"eta": 80.0, "type": "G2", "element": "Pd"}, {"zeta": 1.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 1.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 2.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "O"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": 1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "O"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["O", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}, {"zeta": 4.0, "elements": ["Pd", "Pd"], "type": "G4", "gamma": -1.0, "eta": 0.005}]} fingerprints_range = {"O": [[1.2034320541645842, 1.2325033032109112], [6.126415779807456, 6.978993471872114], [0.9968712422847974, 1.0238339591857768], [3.8381486000216656, 4.6059867092450295], [0.895627825206874, 0.9671954702902308], [2.583663009213816, 3.2975746566094606], [0.7964913272920149, 0.9263856740488036], [1.3358420027866575, 1.987698731796872], [0.6086945405610695, 0.8469928578399282], [0.2529142293461268, 0.7771634549572402], [0.3914378098148111, 0.7306361163848886], [0.020116896658220102, 0.35759863784953067], [0.16188160578588526, 0.5436809953654286], [0.00018177869062004963, 0.12577780799256955], [2.9283890234835495, 2.960848034649544], [30.849062520667466, 34.34079115772228], [74.38292698900159, 81.77213709053859], [1.6948136646289693, 1.803559109943061], [20.032647223918715, 25.204662559464523], [46.30668067734771, 58.99262463422656], [2.37554239540103, 2.7402971203873707], [21.915472638677855, 25.585727302527907], [54.57445823130601, 61.34907477324089], [1.109508025380456, 1.6154672068468818], [11.916848644888729, 17.04495978743268], [27.88887260037905, 39.18415587653102], [1.8928302257282086, 2.5827385383035284], [14.565545948279196, 17.941552668205755], [36.94672253980532, 42.672289485060745], [0.6553641770222022, 1.4212816597490932], [6.00446500499159, 10.95563925572871], [15.456097104319266, 24.846009591495683]], "Pd": [[0.8893801678132963, 2.186742268993366], [5.681935730435191, 5.801357868163544], [0.28325235730086895, 1.7195167772827762], [3.319274384132528, 3.4278827090091513], [0.08915750058756017, 1.5653576140941878], [2.053165118375831, 2.1493466057807606], [0.009285644347874652, 1.3396267483639697], [0.8813449647319378, 0.949755776664418], [1.4022708468796206e-05, 0.8694356341233072], [0.08961848129998626, 0.10519747021560875], [2.5350651988745783e-10, 0.4474694168207284], [0.002199096348873827, 0.002942664869970825], [1.3547037121993997e-19, 0.13803579787982578], [1.3829812001140997e-06, 2.3283471452833342e-06], [2.245260381205755, 6.506711057752208], [30.80688145836148, 46.62802877467697], [63.159691665137835, 63.42318872782115], [1.4470182218367846, 3.5407788975262466], [16.920091949041364, 27.974461180856085], [43.372163387951744, 45.106267442742194], [2.1007039650495236, 5.235507846017004], [24.98117242089699, 38.28678610359639], [47.903805932396644, 48.16729306561395], [0.8039272162816901, 2.5404648021947756], [11.094382911576862, 19.43921017240714], [28.14489163971961, 29.853649179126506], [2.0677437396917027, 3.980577524358615], [18.507476335936385, 30.408978128204563], [33.915660566694726, 34.201570710274964], [0.2569160731561561, 1.5411619576089908], [4.8043897598894185, 13.387133883397796], [17.225764659147405, 18.83859512207448]]} weights = {"O": {1: np.matrix([[0.00036942165470836563, 0.022852891328080965, -0.007763989520407272, 0.0017904084613678817, -0.04680830976127531], [-0.011220619170475267, -0.031465481900468384, -0.0185587250581268, 0.00029876794872765733, -0.03270538302430687], [0.009269384571647388, -0.004439491584362951, 0.02041622613407708, -0.04075220241750707, -0.004384443250886716], [-0.02045226889653854, -0.04085271674587569, -0.0007470148939682439, 0.017448937916376722, -0.03247360282480993], [0.014076539841285075, -0.0006001148651055555, -0.011206188631385075, 0.036131770356541804, 0.04019195568663911], [0.04438555375359607, -0.03630318854778723, - 0.011787189723001579, 0.03403384156560013, 0.015653363757362176], [0.02134038436971622, 0.000554719592425923, -0.04353602059838731, 0.02829112575071807, -0.010315738192632054], [-0.009864186941597866, 0.025867111325423034, -0.030222981254973712, -0.009255262808615411, -0.0047940678082599025], [0.009775595855839286, -0.004398102065676125, -0.00195136837351699, -0.0015883410280669308, 0.03528054083271703], [0.0088457892425432, -0.0017778202887624855, -0.030182606288789264, 0.03153096931177092, -0.02709527292127762], [-0.02911935923819357, -0.011844856703362105, 0.03358589444051113, 0.007149386960731488, -0.007590532737964187], [-0.03944400124516653, 0.03562647918045643, -0.041584201666104756, -0.03482985747462908, -0.045374395214468496], [0.019057890033725933, -0.012580031773554046, 0.04290707878850142, 0.04177600131985214, -0.03500384259370384], [-0.02033084113684249, -0.01111345650394805, -0.005485434669132497, 0.03554246348547074, 0.031421467582530324], [-0.03310168568774624, 0.04617363212275834, 0.03868456178440169, 0.012151585419746959, -0.007359447003748548], [0.044255356329426065, 0.036122120043098505, 0.001842950538131695, -0.01615761183192349, -0.03771427943410247], [-0.0381118034639101, -0.04643318160382238, 0.02900519652241655, -0.008475138348622263, 0.021893066028991853], [0.016038314042298385, 0.03545540262812917, -0.031220884269865096, -0.033670838618425646, 0.04684810506588663], [0.037697271308168634, -0.04250612661317486, 0.0028173761257807364, 0.04503591051281573, -0.005888259820159045], [-0.01688674535740177, 0.03765441774468983, 0.040162723331136185, 0.023291060425779497, 0.01875483057892434], [0.009559969717541077, -0.010986361005406543, 0.017717618257908102, 0.021594452542040676, 0.00668490554203105], [0.02899572788647327, 0.03884753546552183, 0.0334345646492913, -0.0009724588802520473, 0.008901825903656319], [0.04472782971579241, 0.020125743534124996, 0.018466622131502394, 0.014248370483492187, 0.02954224116911444], [0.018038582886592464, 0.007882237781735343, -0.005639481686277245, -0.030317048204748388, 0.011443284253329196], [-0.014574589075944028, 0.027312879897418138, -0.0052516221359138054, -0.02858166510190807, -0.0218508786228111], [-0.019062166466149163, -0.0421343628780219, -0.0292511219030615, -0.04063165343284807, -0.026551753085291934], [-0.006973189792228912, 0.018725587327618767, 0.037936857142053707, 0.011375377365914208, -0.03823975980860963], [-0.03087795180506949, -0.002166181826768615, -0.009411940441267343, 0.008062289773496219, 0.03143133615872179], [0.022767389458292583, -0.032719990839286985, 0.010234126834754581, -0.0025988597425086815, 0.012893424785935387], [0.03729503214821439, -0.04055234881977389, -0.033180455803208164, -0.003962067731434399, -0.04089277483943934], [-0.005215540749534078, 0.013163002568367034, 0.03980552568163612, 0.00803385354609431, 7.658166702390057e-05], [0.013936695364567375, 0.017657437899754047, 0.027548202328624413, -0.0008692880197060243, 0.032762776542753225], [0.0, 0.0, 0.0, 0.0, 0.0]]), 2: np.matrix([[-0.13315704861614908, 0.21616481887190436, 0.07102546888848049, -0.2758348650926486, -0.12434812671933795], [-0.024314957289090222, -0.16392515185308187, 0.2058922926890992, 0.2154935160814611, 0.11014812360618259], [-0.08133895309316427, -0.1937923029504461, 0.206977413616443, 0.03575405386811248, -0.10559013113242327], [0.1469937256217183, 0.07621742865022896, 0.08882575726900893, -0.2577928927812111, 0.2670748892517893], [-0.141370342762172, -0.23738939477247786, -0.06633785630500305, -0.24779722808875726, 0.17677488447247947], [0.0, 0.0, 0.0, 0.0, 0.0]]), 3: np.matrix([[0.0377280558085506], [0.013842778159018243], [-0.29408570195900635], [0.19529036441834974], [-0.16745509851929688], [0.0]])}, "Pd": {1: np.matrix([[0.008070279669324429, -0.04006333630883027, -0.04312742429320118, -0.03942171198922403, -0.04540662900302544], [0.01339814716182161, -0.022961503636403632, -0.006969046155031772, 0.01539617792272549, -0.02587844848147742], [0.045033334892680674, 0.0034430687137840393, 0.02405223418836909, -0.035506042140031155, 0.021328894351546446], [-0.04416667286322164, -0.03993519399665675, 0.032311654583997304, -0.03745738975064494, 0.006061355326268905], [-0.043438846516273555, 0.020424466564239616, -0.03712722505187403, -0.04417848105963802, -0.008777813735156417], [0.03965347387678732, -0.01799472378269024, 0.0362866746012956, 0.009704740166992, 0.0004118619760827419], [-0.03180969154106336, -0.006918591959222585, 0.014099398062742227, -0.022931651589221756, 0.03148626725702887], [0.04573128229126357, 0.016654751576744925, -0.028910689496630722, 0.02242435838167882, -0.02783084152657823], [0.030147617474449384, -0.009580788002314114, 0.026913224902892594, -0.006350898911528513, -0.01580260272955647], [-0.03128280563473111, -0.044359797916295726, -0.0455871021838766, -0.022323871191166217, -0.025520059574284607], [-0.004213681746207731, -0.027963910926939888, -0.03734025976436221, -0.029904058599404374, -0.023362113055890702], [0.03140805808988659, -0.01625862158802977, -0.012926251592534549, 0.0199950518624378, 0.00017000814436556738], [0.03611338398893238, -0.04064588225668243, -0.03548786885528668, -0.034119876099748085, -0.03249791207428783], [0.04302813264222295, -0.031784410672976354, -0.0018505347572984332, -0.02619493567773821, -0.009963146880811465], [-0.00382761556441661, 0.02051612655974898, -0.015084868592703193, 0.036644660445905974, 0.024267396930057042], [0.0027419126458524262, -0.01875730493117643, 0.042029556463568374, -0.033491496522005004, 0.04664358315093048], [-0.00857053904710025, 0.004386575075249165, -0.03681921382606547, 0.024055769666913862, -0.006710822409842235], [0.01600071354805395, -0.03619212782962617, -0.007657861036073181, 0.04579883161005442, -0.027272703382017247], [0.024782613292205463, 0.02454697361926271, 0.014219326292126383, -0.03120859763819632, 0.019746899921596867], [-0.008107835898640163, -0.02411112524744128, 0.01680784294783398, -0.03942450668164303, -0.02148968897141828], [0.006160769106771449, -0.02608742029162942, -0.03445574192255718, 0.011100495475242236, -0.011890887277678633], [0.019265102424069563, -0.019510992393145597, -0.039330197040643305, 0.028930252847621296, 0.04535579375056527], [0.0003841258275426168, -0.03140536534416318, 0.004402540856303851, -0.006596225898408456, -0.012287524451218383], [0.032434589752896065, -0.038422865723774166, 0.04121673691259908, 0.026471126594987765, -0.045659510547159485], [0.016693221128737612, 0.033475787637348264, -0.01216104367054778, -0.04682497168901334, -0.025748662607038442], [-0.030035984906774393, 0.03528987279339724, 0.01842649225978525, 0.013967345908646303, 0.030368471307811548], [-0.004245382943207754, 0.004346310546406856, 0.04395403376516939, -0.03528225866346128, 0.040526584371759225], [-0.026240373867317947, -0.02790624801091845, 0.033248579584558235, -0.03456761843589754, -0.00921953855906435], [-0.04029772119462781, 0.03944849938380114, 0.03367466933743388, -0.04654081205741553, -0.02559442696348037], [-0.019162242379646047, -0.0074198239538341496, -0.03481645962457279, 0.0023221563528588313, -0.01362951107641086], [-0.04359327067093935, 0.008182459343197494, -0.004311982184810589, 0.013459029430653538, -0.02593952116632298], [0.03419829018664716, -0.02909906291417496, 0.0450381809975251, 0.04636855435694584, 0.004474211596899327], [0.0, 0.0, 0.0, 0.0, 0.0]]), 2: np.matrix([[0.07339646055942084, -0.22682470946032204, -0.07464451676678477, -0.21765816530655968, 0.10447399748556846], [-0.07339330664074986, -0.2620525555813218, -0.010761218306725495, 0.07390075065002266, 0.11039186125577433], [-0.17516748044584285, -0.2837828871933906, -0.02085650668287642, 0.08755824083276131, 0.07220039658405131], [0.23974597425595473, 0.24760019759492297, -0.22060915253115443, -0.28310518337421325, -0.016857214958102662], [0.11687787432599622, -0.10151689213238121, 0.18735099239621017, 0.21356695418645139, -0.240568272158666], [0.0, 0.0, 0.0, 0.0, 0.0]]), 3: np.matrix([[0.05906457619187622], [-0.29300196568707426], [-0.018802515167880285], [-0.2723126668305828], [0.22668984898833738], [0.0]])}} scalings = {"O": {"intercept": 4.2468934359280288, "slope": 3.1965614888424687}, "Pd": {"intercept": 4.2468934359280288, "slope": 3.1965614888424687}} def test(): """Guassian/Neural force call. Checks consistency of pure-python and fortran versions. """ images = make_images() for fortran in [False, True]: label = 'forcecall/%s' % fortran calc = Amp(descriptor=Gaussian(cutoff=cutoff, Gs=Gs, fortran=fortran,), model=NeuralNetwork(hiddenlayers=hiddenlayers, weights=weights, scalings=scalings, activation=activation, mode='atom-centered', fprange=fingerprints_range, fortran=fortran,), label=label,) if fortran is False: reference_energies = [calc.get_potential_energy(image) for image in images] else: predicted_energies = [calc.get_potential_energy(image) for image in images] for image_no in range(len(predicted_energies)): assert (abs(predicted_energies[image_no] - reference_energies[image_no]) < 10.**(-5.)), \ 'Calculated energy value of image %i by \ fortran version is not consistent with the \ value of python version.' % (image_no + 1) if fortran is False: reference_forces = [calc.get_forces(image) for image in images] else: predicted_forces = [calc.get_forces(image) for image in images] for image_no in range(len(predicted_forces)): for index in range(np.shape(predicted_forces[image_no])[0]): for k in range(np.shape(predicted_forces[image_no])[1]): assert (abs(predicted_forces[image_no][index][k] - reference_forces[image_no][index][k]) < 10.**(-5.)), \ 'Calculated %i force of atom %i of \ image %i by fortran version is not \ consistent with the value of python \ version.' % (k, index, image_no + 1) # if __name__ == '__main__': test() amp-0.6/tests/consistency_test/zernike_test.py0000644000175000017500000000636313137634440021653 0ustar muammarmuammar""" This script creates a list of three images. It then calculates Zernike fingerprints of images with and without fortran modules on different number of cores, and checks consistency between them. """ import numpy as np from ase import Atoms from amp.descriptor.zernike import Zernike from amp.utilities import hash_images, assign_cores def make_images(): """Makes test images.""" images = [Atoms(symbols='Pd3O2', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[7.78, 0., 0.], [0., 5.50129076, 0.], [0., 0., 15.37532269]]), positions=np.array( [[3.89, 0., 8.37532269], [0., 2.75064538, 8.37532269], [3.89, 2.75064538, 8.37532269], [5.835, 1.37532269, 8.5], [5.835, 7.12596807, 8.]]))] return images def test(): """Zernike fingerprints consistency. Tests that pure-python and fortran, plus different number of cores give same results. """ images = make_images() images = hash_images(images, ordered=True) ref_fps = {} ref_fp_primes = {} count = 0 for fortran in [True, False]: for ncores in range(1, 3): cores = assign_cores(ncores) descriptor = Zernike(fortran=fortran, dblabel='Zernike-%s-%d' % (fortran, ncores)) descriptor.calculate_fingerprints(images, parallel={'cores': cores, 'envcommand': None}, log=None, calculate_derivatives=True) for hash, image in images.items(): if count == 0: ref_fps[hash] = descriptor.fingerprints[hash] ref_fp_primes[hash] = descriptor.fingerprintprimes[hash] else: fps = descriptor.fingerprints[hash] # Checking consistency between fingerprints for (element1, afp1), \ (element2, afp2) in zip(ref_fps[hash], fps): assert element1 == element2, \ 'fortran-python consistency for Zernike ' 'fingerprints broken!' for _, __ in zip(afp1, afp2): assert (abs(_ - __) < (10 ** (-10.))), \ 'fortran-python consistency for Zernike ' 'fingerprints broken!' # Checking consistency between fingerprint primes fpprime = descriptor.fingerprintprimes[hash] for key, value in ref_fp_primes[hash].items(): for _, __ in zip(value, fpprime[key]): assert (abs(_ - __) < (10 ** (-10.))), \ 'fortran-python consistency for Zernike ' 'fingerprint primes broken!' count += 1 if __name__ == '__main__': test() amp-0.6/tests/consistency_test/gaussian_test.py0000644000175000017500000001157013137634440022012 0ustar muammarmuammar""" This script creates a list of three images. It then calculates Gaussian fingerprints of images with and without fortran modules on different number of cores, and check consistency between them. """ import numpy as np from ase import Atoms from amp.descriptor.gaussian import Gaussian from amp.utilities import hash_images # Making the list of images def make_images(): """Makes test images.""" images = [Atoms(symbols='Pd3O2', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[7.78, 0., 0.], [0., 5.50129076, 0.], [0., 0., 15.37532269]]), positions=np.array( [[3.89, 0., 8.37532269], [0., 2.75064538, 8.37532269], [3.89, 2.75064538, 8.37532269], [5.835, 1.37532269, 8.5], [5.835, 7.12596807, 8.]])), Atoms(symbols='Pd3O2', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[7.78, 0., 0.], [0., 5.50129076, 0.], [0., 0., 15.37532269]]), positions=np.array( [[3.88430768e+00, 5.28005966e-03, 8.36678641e+00], [-1.01122240e-02, 2.74577426e+00, 8.37861758e+00], [3.88251383e+00, 2.74138906e+00, 8.37087611e+00], [5.82067191e+00, 1.19156898e+00, 8.97714483e+00], [5.83355445e+00, 7.53318593e+00, 8.50142020e+00]])), Atoms(symbols='Pd3O2', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[7.78, 0., 0.], [0., 5.50129076, 0.], [0., 0., 15.37532269]]), positions=np.array( [[3.87691266e+00, 9.29708987e-03, 8.35604207e+00], [-1.29700138e-02, 2.74373753e+00, 8.37941484e+00], [3.86813484e+00, 2.73488653e+00, 8.36395999e+00], [5.80386111e+00, 7.98192190e-01, 9.74324179e+00], [5.83223956e+00, 8.23855393e+00, 9.18295137e+00]]))] return images def test(): """Gaussian fingerprints consistency. Tests that pure-python and fortran, plus different number of cores give same results. """ images = make_images() images = hash_images(images, ordered=True) ref_fps = {} ref_fp_primes = {} count = 0 for fortran in [False, True]: for cores in range(1, 2): descriptor = Gaussian(fortran=fortran, dblabel='Gaussian-%s-%d' % (fortran, cores)) descriptor.calculate_fingerprints(images, parallel={'cores': cores}, log=None, calculate_derivatives=True) for hash, image in images.items(): if count == 0: ref_fps[hash] = descriptor.fingerprints[hash] ref_fp_primes[hash] = descriptor.fingerprintprimes[hash] else: fps = descriptor.fingerprints[hash] # Checking consistency between fingerprints for (element1, afp1), \ (element2, afp2) in zip(ref_fps[hash], fps): assert element1 == element2, \ 'fortran-python consistency for Gaussian ' 'fingerprints broken!' for _, __ in zip(afp1, afp2): assert (abs(_ - __) < 10 ** (-15.)), \ 'fortran-python consistency for Gaussian ' 'fingerprints broken!' # Checking consistency between fingerprint primes fpprime = descriptor.fingerprintprimes[hash] for key, value in ref_fp_primes[hash].items(): for _, __ in zip(value, fpprime[key]): assert (abs(_ - __) < 10 ** (-15.)), \ 'fortran-python consistency for Gaussian ' 'fingerprint primes broken!' count += 1 if __name__ == '__main__': test() amp-0.6/tests/CuOPd_test/0002755000175000017500000000000013137634440015176 5ustar muammarmuammaramp-0.6/tests/CuOPd_test/gaussian_neural_test/0002755000175000017500000000000013142667007021416 5ustar muammarmuammaramp-0.6/tests/CuOPd_test/gaussian_neural_test/train_test.py0000644000175000017500000006716513137634440024160 0ustar muammarmuammar""" Exact Gaussian-neural scheme loss function, energy loss and force loss for five different non-periodic configurations and three three different periodic configurations have been calculated in Mathematica. This script checks the values calculated by the code during training with and without fortran modules and also on different number of cores. """ import numpy as np from collections import OrderedDict from ase import Atoms from ase.calculators.emt import EMT from amp import Amp from amp.descriptor.gaussian import Gaussian from amp.model.neuralnetwork import NeuralNetwork from amp.model import LossFunction from amp.regression import Regressor try: from ase import __version__ as aseversion except ImportError: # We're on ASE 3.10 or older from ase.version import version as aseversion aseversion = int(aseversion.split('.')[-2]) # The test function for non-periodic systems convergence = {'energy_rmse': 10.**10., 'energy_maxresid': 10.**10., 'force_rmse': 10.**10., 'force_maxresid': 10.**10., } regressor = Regressor(optimizer='BFGS') def non_periodic_0th_bfgs_step_test(): """Gaussian/Neural training non-periodic standard test. Compares results to that expected from separate mathematica calculations. """ images = [Atoms(symbols='PdOPd2', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[0., 0., 0.], [0., 2., 0.], [0., 0., 3.], [1., 0., 0.]])), Atoms(symbols='PdOPd2', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[0., 1., 0.], [1., 2., 1.], [-1., 1., 2.], [1., 3., 2.]])), Atoms(symbols='PdO', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[2., 1., -1.], [1., 2., 1.]])), Atoms(symbols='Pd2O', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[-2., -1., -1.], [1., 2., 1.], [3., 4., 4.]])), Atoms(symbols='Cu', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[0., 0., 0.]]))] for image in images: image.set_calculator(EMT()) image.get_potential_energy(apply_constraint=False) image.get_forces(apply_constraint=False) # Parameters Gs = {'O': [{'type': 'G2', 'element': 'Pd', 'eta': 0.8}, {'type': 'G4', 'elements': [ 'Pd', 'Pd'], 'eta':0.2, 'gamma':0.3, 'zeta':1}, {'type': 'G4', 'elements': ['O', 'Pd'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}], 'Pd': [{'type': 'G2', 'element': 'Pd', 'eta': 0.2}, {'type': 'G4', 'elements': ['Pd', 'Pd'], 'eta':0.9, 'gamma':0.75, 'zeta':1.5}, {'type': 'G4', 'elements': ['O', 'Pd'], 'eta':0.4, 'gamma':0.3, 'zeta':4}], 'Cu': [{'type': 'G2', 'element': 'Cu', 'eta': 0.8}, {'type': 'G4', 'elements': ['Cu', 'O'], 'eta':0.2, 'gamma':0.3, 'zeta':1}, {'type': 'G4', 'elements': ['Cu', 'Cu'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}]} hiddenlayers = {'O': (2,), 'Pd': (2,), 'Cu': (2,)} weights = OrderedDict([('O', OrderedDict([(1, np.matrix([[-2.0, 6.0], [3.0, -3.0], [1.5, -0.9], [-2.5, -1.5]])), (2, np.matrix([[5.5], [3.6], [1.4]]))])), ('Pd', OrderedDict([(1, np.matrix([[-1.0, 3.0], [2.0, 4.2], [1.0, -0.7], [-3.0, 2.0]])), (2, np.matrix([[4.0], [0.5], [3.0]]))])), ('Cu', OrderedDict([(1, np.matrix([[0.0, 1.0], [-1.0, -2.0], [2.5, -1.9], [-3.5, 0.5]])), (2, np.matrix([[0.5], [1.6], [-1.4]]))]))]) scalings = OrderedDict([('O', OrderedDict([('intercept', -2.3), ('slope', 4.5)])), ('Pd', OrderedDict([('intercept', 1.6), ('slope', 2.5)])), ('Cu', OrderedDict([('intercept', -0.3), ('slope', -0.5)]))]) # Correct values if aseversion < 12: # EMT values have changed from 3.12.0 version ref_loss = 7144.8107853579895 ref_energyloss = (24.318837496016506 ** 2.) * 5 ref_forceloss = (144.70282477494519 ** 2.) * 5 ref_dloss_dparameters = np.array([0, 0, 0, 0, 0, 0, 0.01374139170953901, 0.36318423812749656, 0.028312691567496464, 0.6012336354445753, 0.9659002689921986, -1.289777005924742, -0.5718960934643078, -2.642566722179569, -1.196039924610482, 0, 0, -2.72563797131018, -0.9080181024866707, -0.7739948323226851, -0.29157894253717415, -2.0599829042717404, -0.6156374289895887, -0.006086517460749253, -0.829678548408266, 0.0008092646745710161, 0.04161302703491613, 0.0034264690790135606, -0.957800456897051, -0.006281929606579444, -0.2883588477371198, -4.245777410962108, -4.3174120941045535, -8.02385959091948, -3.240512651984099, -27.289862194988853, -26.8177742762544, -82.45107056051073, -80.68167683508715]) ref_energy_maxresid = 54.21915548269209 ref_force_maxresid = 791.6736436232306 else: ref_loss = 7144.807220773296 ref_energyloss = (24.318829702548342 ** 2.) * 5 ref_forceloss = (144.70279593472887 ** 2.) * 5 ref_dloss_dparameters = np.array([0, 0, 0, 0, 0, 0, 0.01374139170953901, 0.36318423812749656, 0.028312691567496464, 0.6012336354445753, 0.9659002689921986, -1.2897765357544038, -0.5718958286530584, -2.642565840915077, -1.1960394346870424, 0, 0, -2.7256370964673238, -0.9080177898160631, -0.7739945904033205, -0.29157882294526083, -2.0599825024556027, -0.6156371996742152, -0.006086514109432934, -0.8296782839032163, 0.0008092653341775424, 0.04161306816722683, 0.0034264692325982156, -0.9578001030483714, -0.006281927374160914, -0.28835874344086, -4.245775886469167, -4.317410633818672, -8.02385959091948, -3.240512651984099, -27.289853042932705, -26.81776520493048, -82.45104200076496, -80.68164887277251]) ref_energy_maxresid = 54.21913802238612 ref_force_maxresid = 791.6734866205463 # Testing pure-python and fortran versions of Gaussian-neural on different # number of processes for fortran in [False, True]: for cores in range(1, 6): label = 'train-nonperiodic/%s-%i' % (fortran, cores) print(label) calc = Amp(descriptor=Gaussian(cutoff=6.5, Gs=Gs, fortran=fortran,), model=NeuralNetwork(hiddenlayers=hiddenlayers, weights=weights, scalings=scalings, activation='sigmoid', regressor=regressor, fortran=fortran,), label=label, dblabel=label, cores=cores) lossfunction = LossFunction(convergence=convergence) calc.model.lossfunction = lossfunction calc.train(images=images,) diff = abs(calc.model.lossfunction.loss - ref_loss) print("diff at 204 =", diff) assert (diff < 10.**(-10.)), \ 'Calculated value of loss function is wrong!' diff = abs(calc.model.lossfunction.energy_loss - ref_energyloss) assert (diff < 10.**(-10.)), \ 'Calculated value of energy per atom RMSE is wrong!' diff = abs(calc.model.lossfunction.force_loss - ref_forceloss) assert (diff < 10 ** (-10.)), \ 'Calculated value of force RMSE is wrong!' diff = abs(calc.model.lossfunction.energy_maxresid - ref_energy_maxresid) assert (diff < 10.**(-10.)), \ 'Calculated value of energy per atom max residual is wrong!' diff = abs(calc.model.lossfunction.force_maxresid - ref_force_maxresid) assert (diff < 10 ** (-10.)), \ 'Calculated value of force max residual is wrong!' for _ in range(len(ref_dloss_dparameters)): diff = abs(calc.model.lossfunction.dloss_dparameters[_] - ref_dloss_dparameters[_]) assert(diff < 10 ** (-12.)), \ "Calculated value of loss function derivative is wrong!" dblabel = label secondlabel = '_' + label calc = Amp(descriptor=Gaussian(cutoff=6.5, Gs=Gs, fortran=fortran,), model=NeuralNetwork(hiddenlayers=hiddenlayers, weights=weights, scalings=scalings, activation='sigmoid', regressor=regressor, fortran=fortran,), label=secondlabel, dblabel=dblabel, cores=cores) lossfunction = LossFunction(convergence=convergence) calc.model.lossfunction = lossfunction calc.train(images=images,) diff = abs(calc.model.lossfunction.loss - ref_loss) assert (diff < 10.**(-10.)), \ 'Calculated value of loss function is wrong!' diff = abs(calc.model.lossfunction.energy_loss - ref_energyloss) assert (diff < 10.**(-10.)), \ 'Calculated value of energy per atom RMSE is wrong!' diff = abs(calc.model.lossfunction.force_loss - ref_forceloss) assert (diff < 10 ** (-10.)), \ 'Calculated value of force RMSE is wrong!' diff = abs(calc.model.lossfunction.energy_maxresid - ref_energy_maxresid) assert (diff < 10.**(-10.)), \ 'Calculated value of energy per atom max residual is wrong!' diff = abs(calc.model.lossfunction.force_maxresid - ref_force_maxresid) assert (diff < 10 ** (-10.)), \ 'Calculated value of force max residual is wrong!' for _ in range(len(ref_dloss_dparameters)): diff = abs(calc.model.lossfunction.dloss_dparameters[_] - ref_dloss_dparameters[_]) assert(diff < 10 ** (-12.)), \ 'Calculated value of loss function derivative is wrong!' # The test function for periodic systems and first BFGS step def periodic_0th_bfgs_step_test(): """Gaussian/Neural training periodic standard test. Compares results to that expected from separate mathematica calculations. """ # Making the list of images images = [Atoms(symbols='PdOPd', pbc=np.array([True, False, False], dtype=bool), cell=np.array( [[2., 0., 0.], [0., 2., 0.], [0., 0., 2.]]), positions=np.array( [[0.5, 1., 0.5], [1., 0.5, 1.], [1.5, 1.5, 1.5]])), Atoms(symbols='PdO', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[2., 0., 0.], [0., 2., 0.], [0., 0., 2.]]), positions=np.array( [[0.5, 1., 0.5], [1., 0.5, 1.]])), Atoms(symbols='Cu', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[1.8, 0., 0.], [0., 1.8, 0.], [0., 0., 1.8]]), positions=np.array( [[0., 0., 0.]]))] for image in images: image.set_calculator(EMT()) image.get_potential_energy(apply_constraint=False) image.get_forces(apply_constraint=False) # Parameters Gs = {'O': [{'type': 'G2', 'element': 'Pd', 'eta': 0.8}, {'type': 'G4', 'elements': ['O', 'Pd'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}], 'Pd': [{'type': 'G2', 'element': 'Pd', 'eta': 0.2}, {'type': 'G4', 'elements': ['Pd', 'Pd'], 'eta':0.9, 'gamma':0.75, 'zeta':1.5}], 'Cu': [{'type': 'G2', 'element': 'Cu', 'eta': 0.8}, {'type': 'G4', 'elements': ['Cu', 'Cu'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}]} hiddenlayers = {'O': (2,), 'Pd': (2,), 'Cu': (2,)} weights = OrderedDict([('O', OrderedDict([(1, np.matrix([[-2.0, 6.0], [3.0, -3.0], [1.5, -0.9]])), (2, np.matrix([[5.5], [3.6], [1.4]]))])), ('Pd', OrderedDict([(1, np.matrix([[-1.0, 3.0], [2.0, 4.2], [1.0, -0.7]])), (2, np.matrix([[4.0], [0.5], [3.0]]))])), ('Cu', OrderedDict([(1, np.matrix([[0.0, 1.0], [-1.0, -2.0], [2.5, -1.9]])), (2, np.matrix([[0.5], [1.6], [-1.4]]))]))]) scalings = OrderedDict([('O', OrderedDict([('intercept', -2.3), ('slope', 4.5)])), ('Pd', OrderedDict([('intercept', 1.6), ('slope', 2.5)])), ('Cu', OrderedDict([('intercept', -0.3), ('slope', -0.5)]))]) # Correct values if aseversion < 12: # EMT values have changed from 3.12.0 version ref_loss = 8004.292841411172 ref_energyloss = (43.7360019403031 ** 2.) * 3 ref_forceloss = (137.40994760947325 ** 2.) * 3 ref_dloss_dparameters = np.array([0.08141668748130322, 0.03231235582925534, 0.04388650395738586, 0.017417514465922313, 0.028431276597563077, 0.011283700608814465, 0.0941695726576061, -0.12322258890990219, 0.12679918754154568, 63.53960075374332, 0.01624770019548904, -86.6263955859162, -0.01777752828707744, 86.22415217526024, 0.017745913074496918, 104.58358033298292, -96.73280209888215, -99.09843648905876, -8.302880631972338, -1.2590007162074357, 8.302877346883133, 1.25875988418134, -8.302866610678247, -1.2563833805675353, 28.324298392680998, 28.093155094726413, -29.37874455931869, -11.247473567044866, 11.119951466664787, -87.08582317481387, -20.939485239182346, -125.73267675705365, -35.138524407482116]) else: ref_loss = 8004.287750978173 ref_energyloss = (43.73598563177581 ** 2.) * 3 ref_forceloss = (137.409923023214 ** 2.) * 3 ref_dloss_dparameters = np.array([0.08141663280688925, 0.03231233413027478, 0.043886474485922956, 0.01741750276939638, 0.02843125750487539, 0.011283693031378718, 0.09416950941914284, -0.12322250616122936, 0.1267991023910503, 63.53958764057119, 0.016247696749304368, -86.62637753054923, -0.01777752451341436, 86.22413420485914, 0.01774590930723711, 104.58353326982777, -96.73275667196937, -99.09839026204304, -8.302877823431269, -1.2590002903842232, 8.302874538343092, 1.2587594584335775, -8.302863802141216, -1.2563829555383859, 28.32428881173613, 28.093145591893936, -29.37873462156934, -11.24746601393696, 11.11994399919284, -87.08579155328007, -20.93947792122797, -125.73262989900473, -35.13850819392253]) # Testing pure-python and fortran versions of Gaussian-neural on different # number of processes for fortran in [False, True]: for cores in range(1, 4): label = 'train-periodic/%s-%i' % (fortran, cores) print(label) calc = Amp(descriptor=Gaussian(cutoff=4., Gs=Gs, fortran=fortran,), model=NeuralNetwork(hiddenlayers=hiddenlayers, weights=weights, scalings=scalings, activation='tanh', regressor=regressor, fortran=fortran,), label=label, dblabel=label, cores=cores) lossfunction = LossFunction(convergence=convergence) calc.model.lossfunction = lossfunction calc.train(images=images,) diff = abs(calc.model.lossfunction.loss - ref_loss) print("diff at 414 =", diff) assert (diff < 10.**(-10.)), \ 'Calculated value of loss function is wrong!' diff = abs(calc.model.lossfunction.energy_loss - ref_energyloss) assert (diff < 10.**(-10.)), \ 'Calculated value of energy per atom RMSE is wrong!' diff = abs(calc.model.lossfunction.force_loss - ref_forceloss) assert (diff < 10 ** (-9.)), \ 'Calculated value of force RMSE is wrong!' for _ in range(len(ref_dloss_dparameters)): diff = abs(calc.model.lossfunction.dloss_dparameters[_] - ref_dloss_dparameters[_]) assert(diff < 10 ** (-10.)), \ 'Calculated value of loss function derivative is wrong!' dblabel = label secondlabel = '_' + label calc = Amp(descriptor=Gaussian(cutoff=4., Gs=Gs, fortran=fortran), model=NeuralNetwork(hiddenlayers=hiddenlayers, weights=weights, scalings=scalings, activation='tanh', regressor=regressor, fortran=fortran,), label=secondlabel, dblabel=dblabel, cores=cores) lossfunction = LossFunction(convergence=convergence) calc.model.lossfunction = lossfunction calc.train(images=images,) diff = abs(calc.model.lossfunction.loss - ref_loss) assert (diff < 10.**(-10.)), \ 'Calculated value of loss function is wrong!' diff = abs(calc.model.lossfunction.energy_loss - ref_energyloss) assert (diff < 10.**(-10.)), \ 'Calculated value of energy per atom RMSE is wrong!' diff = abs(calc.model.lossfunction.force_loss - ref_forceloss) assert (diff < 10 ** (-9.)), \ 'Calculated value of force RMSE is wrong!' for _ in range(len(ref_dloss_dparameters)): diff = abs(calc.model.lossfunction.dloss_dparameters[_] - ref_dloss_dparameters[_]) assert(diff < 10 ** (-10.)), \ 'Calculated value of loss function derivative is wrong!' if __name__ == '__main__': non_periodic_0th_bfgs_step_test() periodic_0th_bfgs_step_test() amp-0.6/tests/CuOPd_test/gaussian_neural_test/force_call_test_tflow.py0000644000175000017500000004346213137634440026341 0ustar muammarmuammar""" Exact Gaussian-neural scheme forces and energies of five different non-periodic configurations and three different periodic configurations have been calculated in Mathematica, and are given below. This script checks the values calculated by the code with and without fortran modules. """ import sys import numpy as np from collections import OrderedDict from ase import Atoms from amp import Amp from amp.descriptor.gaussian import Gaussian def check_perform(): """Determines whether or not to perform the test. This should only perform the test if the python version is 2.x and tensorflow is installed. If returns False (meaning don't peform test), also supplies the reason.""" if sys.version_info >= (3,): return False, 'amp.model.tflow not supported in python3.' try: import tensorflow except ImportError: return False, 'Tensorflow not installed.' return True, '' def non_periodic_test(): """Gaussian/tflowNeural non-periodic.""" perform, reason = check_perform() if not perform: print('Skipping this test because {}'.format(reason)) return from amp.model.tflow import NeuralNetwork # Making the list of non-periodic images images = [Atoms(symbols='PdOPd2', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[0., 0., 0.], [0., 2., 0.], [0., 0., 3.], [1., 0., 0.]])), Atoms(symbols='PdOPd2', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[0., 1., 0.], [1., 2., 1.], [-1., 1., 2.], [1., 3., 2.]])), Atoms(symbols='PdO', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[2., 1., -1.], [1., 2., 1.]])), Atoms(symbols='Pd2O', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[-2., -1., -1.], [1., 2., 1.], [3., 4., 4.]])), Atoms(symbols='Cu', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[0., 0., 0.]]))] # Correct energies and forces correct_energies = [14.231186811226152, 14.327219917287948, 5.5742510565528285, 9.41456771216968, -0.5019297954597407] correct_forces = \ [[[-0.05095024246182649, -0.10709193432146558, -0.09734321482638622], [-0.044550772904033635, 0.2469763195486647, -0.07617425912869778], [-0.02352490951707703, -0.050782839419131864, 0.24409220250631508], [0.11902592488293715, -0.08910154580806727, -0.07057472855123109]], [[-0.024868720575099375, -0.07417891957113862, -0.12121240797223251], [0.060156158438252574, 0.017517013378773042, -0.020047135079325505], [-0.10901144291312388, -0.06671262448352767, 0.06581556263014315], [0.07372400504997068, 0.12337453067589325, 0.07544398042141486]], [[0.10151747265164626, -0.10151747265164626, -0.20303494530329252], [-0.10151747265164626, 0.10151747265164626, 0.20303494530329252]], [[-0.00031177673224312745, -0.00031177673224312745, -0.0002078511548287517], [0.004823209772264884, 0.004823209772264884, 0.006975000714861393], [-0.004511433040021756, -0.004511433040021756, -0.006767149560032641]], [[0.0, 0.0, 0.0]]] # Parameters Gs = {'O': [{'type': 'G2', 'element': 'Pd', 'eta': 0.8}, {'type': 'G4', 'elements': [ 'Pd', 'Pd'], 'eta':0.2, 'gamma':0.3, 'zeta':1}, {'type': 'G4', 'elements': ['O', 'Pd'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}], 'Pd': [{'type': 'G2', 'element': 'Pd', 'eta': 0.2}, {'type': 'G4', 'elements': ['Pd', 'Pd'], 'eta':0.9, 'gamma':0.75, 'zeta':1.5}, {'type': 'G4', 'elements': ['O', 'Pd'], 'eta':0.4, 'gamma':0.3, 'zeta':4}], 'Cu': [{'type': 'G2', 'element': 'Cu', 'eta': 0.8}, {'type': 'G4', 'elements': ['Cu', 'O'], 'eta':0.2, 'gamma':0.3, 'zeta':1}, {'type': 'G4', 'elements': ['Cu', 'Cu'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}]} hiddenlayers = {'O': (2, 1), 'Pd': (2, 1), 'Cu': (2, 1)} weights = OrderedDict([('O', OrderedDict([(1, np.matrix([[-2.0, 6.0], [3.0, -3.0], [1.5, -0.9], [-2.5, -1.5]])), (2, np.matrix([[5.5], [3.6], [1.4]]))])), ('Pd', OrderedDict([(1, np.matrix([[-1.0, 3.0], [2.0, 4.2], [1.0, -0.7], [-3.0, 2.0]])), (2, np.matrix([[4.0], [0.5], [3.0]]))])), ('Cu', OrderedDict([(1, np.matrix([[0.0, 1.0], [-1.0, -2.0], [2.5, -1.9], [-3.5, 0.5]])), (2, np.matrix([[0.5], [1.6], [-1.4]]))]))]) scalings = OrderedDict([('O', OrderedDict([('intercept', -2.3), ('slope', 4.5)])), ('Pd', OrderedDict([('intercept', 1.6), ('slope', 2.5)])), ('Cu', OrderedDict([('intercept', -0.3), ('slope', -0.5)]))]) fingerprints_range = {"Cu": np.array([[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]]), "O": np.array([[0.2139617720858539, 2.258090276328769], [0.0, 1.085656080548734], [0.0, 0.0]]), "Pd": np.array([[0.0, 1.4751761770313006], [0.0, 0.28464992134267897], [0.0, 0.20167521020630502]])} # Testing pure-python and fortran versions of Gaussian-neural force call for fortran in [False, True]: for cores in range(1, 6): label = 'call-nonperiodic/%s-%i' % (fortran, cores) calc = Amp(descriptor=Gaussian(cutoff=6.5, Gs=Gs, fortran=fortran), model=NeuralNetwork(hiddenlayers=hiddenlayers, weights=weights, scalings=scalings, activation='sigmoid', fprange=fingerprints_range), label=label, dblabel=label, cores=cores) predicted_energies = [calc.get_potential_energy(image) for image in images] for image_no in range(len(predicted_energies)): print(predicted_energies[image_no]) print(correct_energies[image_no]) diff = abs(predicted_energies[image_no] - correct_energies[image_no]) assert (diff < 10.**(-3.)), \ 'The predicted energy of image %i is wrong!' % ( image_no + 1) predicted_forces = [calc.get_forces(image) for image in images] for image_no in range(len(predicted_forces)): print('predicted forces:') print(predicted_forces[image_no]) print('correct forces:') print(np.array(correct_forces[image_no])) for index in range(np.shape(predicted_forces[image_no])[0]): for direction in range( np.shape(predicted_forces[image_no])[1]): diff = abs(predicted_forces[image_no][index][ direction] - correct_forces[image_no][index][direction]) assert (diff < 10.**(-3.)), \ 'The predicted %i force of atom %i of image %i ' \ 'is wrong!' % (direction, index, image_no + 1) def periodic_test(): """Gaussian/tflowNeural periodic.""" perform, reason = check_perform() if not perform: print('Skipping this test because {}'.format(reason)) return from amp.model.tflow import NeuralNetwork # Making the list of periodic images images = [Atoms(symbols='PdOPd', pbc=np.array([True, False, False], dtype=bool), cell=np.array( [[2., 0., 0.], [0., 2., 0.], [0., 0., 2.]]), positions=np.array( [[0.5, 1., 0.5], [1., 0.5, 1.], [1.5, 1.5, 1.5]])), Atoms(symbols='PdO', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[2., 0., 0.], [0., 2., 0.], [0., 0., 2.]]), positions=np.array( [[0.5, 1., 0.5], [1., 0.5, 1.]])), Atoms(symbols='Cu', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[1.8, 0., 0.], [0., 1.8, 0.], [0., 0., 1.8]]), positions=np.array( [[0., 0., 0.]]))] # Correct energies and forces correct_energies = [3.8560954326995978, 1.6120748520627273, 0.19433107801410093] correct_forces = \ [[[0.14747720528015523, -3.3010645563584973, 3.3008168318984463], [0.03333579762326405, 9.050780376599887, -0.42608278400777605], [-0.1808130029034193, -5.7497158202413905, -2.8747340478906698]], [[6.5035267996045045 * (10.**(-6.)), -6.503526799604495 * (10.**(-6.)), 0.00010834689201069249], [-6.5035267996045045 * (10.**(-6.)), 6.503526799604495 * (10.**(-6.)), -0.00010834689201069249]], [[0.0, 0.0, 0.0]]] # Parameters Gs = {'O': [{'type': 'G2', 'element': 'Pd', 'eta': 0.8}, {'type': 'G4', 'elements': ['O', 'Pd'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}], 'Pd': [{'type': 'G2', 'element': 'Pd', 'eta': 0.2}, {'type': 'G4', 'elements': ['Pd', 'Pd'], 'eta':0.9, 'gamma':0.75, 'zeta':1.5}], 'Cu': [{'type': 'G2', 'element': 'Cu', 'eta': 0.8}, {'type': 'G4', 'elements': ['Cu', 'Cu'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}]} hiddenlayers = {'O': (2, 1), 'Pd': (2, 1), 'Cu': (2, 1)} weights = OrderedDict([('O', OrderedDict([(1, np.matrix([[-2.0, 6.0], [3.0, -3.0], [1.5, -0.9]])), (2, np.matrix([[5.5], [3.6], [1.4]]))])), ('Pd', OrderedDict([(1, np.matrix([[-1.0, 3.0], [2.0, 4.2], [1.0, -0.7]])), (2, np.matrix([[4.0], [0.5], [3.0]]))])), ('Cu', OrderedDict([(1, np.matrix([[0.0, 1.0], [-1.0, -2.0], [2.5, -1.9]])), (2, np.matrix([[0.5], [1.6], [-1.4]]))]))]) scalings = OrderedDict([('O', OrderedDict([('intercept', -2.3), ('slope', 4.5)])), ('Pd', OrderedDict([('intercept', 1.6), ('slope', 2.5)])), ('Cu', OrderedDict([('intercept', -0.3), ('slope', -0.5)]))]) fingerprints_range = {"Cu": np.array([[2.8636310860653253, 2.8636310860653253], [1.5435994865298275, 1.5435994865298275]]), "O": np.array([[2.9409056366723028, 2.972494902604392], [1.9522542722823606, 4.0720361595017245]]), "Pd": np.array([[2.4629488092411096, 2.6160138774087125], [0.27127576524253594, 0.5898312261433813]])} # Testing pure-python and fortran versions of Gaussian-neural force call for fortran in [False, True]: for cores in range(1, 4): label = 'call-periodic/%s-%i' % (fortran, cores) calc = Amp(descriptor=Gaussian(cutoff=4., Gs=Gs, fortran=fortran), model=NeuralNetwork(hiddenlayers=hiddenlayers, weights=weights, scalings=scalings, activation='tanh', fprange=fingerprints_range, unit_type="double"), label=label, dblabel=label, cores=cores) predicted_energies = [calc.get_potential_energy(image) for image in images] for image_no in range(len(predicted_energies)): print(predicted_energies[image_no]) print(correct_energies[image_no]) diff = abs(predicted_energies[image_no] - correct_energies[image_no]) assert (diff < 10.**(-14.)), \ 'The predicted energy of image %i is wrong!' % ( image_no + 1) predicted_forces = [calc.get_forces(image) for image in images] for image_no in range(len(predicted_forces)): print('predicted forces:') print(predicted_forces[image_no]) print('correct forces:') print(np.array(correct_forces[image_no])) for index in range(np.shape(predicted_forces[image_no])[0]): for direction in range( np.shape(predicted_forces[image_no])[1]): diff = abs(predicted_forces[image_no][index][ direction] - correct_forces[image_no][index][direction]) assert (diff < 10.**(-11.)), \ 'The predicted %i force of atom %i of image' \ ' %i is wrong!' % (direction, index, image_no + 1) if __name__ == '__main__': non_periodic_test() periodic_test() amp-0.6/tests/CuOPd_test/gaussian_neural_test/force_call_test.py0000644000175000017500000004147613137634440025131 0ustar muammarmuammar""" Exact Gaussian-neural scheme forces and energies of five different non-periodic configurations and three different periodic configurations have been calculated in Mathematica, and are given below. This script checks the values calculated by the code with and without fortran modules. """ import numpy as np from ase import Atoms from collections import OrderedDict from amp import Amp from amp.descriptor.gaussian import Gaussian from amp.model.neuralnetwork import NeuralNetwork def non_periodic_test(): """Gaussian/Neural non-periodic standard. Checks that the answer matches that expected from previous Mathematica calculations. """ # Making the list of non-periodic images images = [Atoms(symbols='PdOPd2', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[0., 0., 0.], [0., 2., 0.], [0., 0., 3.], [1., 0., 0.]])), Atoms(symbols='PdOPd2', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[0., 1., 0.], [1., 2., 1.], [-1., 1., 2.], [1., 3., 2.]])), Atoms(symbols='PdO', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[2., 1., -1.], [1., 2., 1.]])), Atoms(symbols='Pd2O', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[-2., -1., -1.], [1., 2., 1.], [3., 4., 4.]])), Atoms(symbols='Cu', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[0., 0., 0.]]))] # Correct energies and forces correct_energies = [14.231186811226152, 14.327219917287948, 5.5742510565528285, 9.41456771216968, -0.5019297954597407] correct_forces = \ [[[-0.05095024246182649, -0.10709193432146558, -0.09734321482638622], [-0.044550772904033635, 0.2469763195486647, -0.07617425912869778], [-0.02352490951707703, -0.050782839419131864, 0.24409220250631508], [0.11902592488293715, -0.08910154580806727, -0.07057472855123109]], [[-0.024868720575099375, -0.07417891957113862, -0.12121240797223251], [0.060156158438252574, 0.017517013378773042, -0.020047135079325505], [-0.10901144291312388, -0.06671262448352767, 0.06581556263014315], [0.07372400504997068, 0.12337453067589325, 0.07544398042141486]], [[0.10151747265164626, -0.10151747265164626, -0.20303494530329252], [-0.10151747265164626, 0.10151747265164626, 0.20303494530329252]], [[-0.00031177673224312745, -0.00031177673224312745, -0.0002078511548287517], [0.004823209772264884, 0.004823209772264884, 0.006975000714861393], [-0.004511433040021756, -0.004511433040021756, -0.006767149560032641]], [[0.0, 0.0, 0.0]]] # Parameters Gs = {'O': [{'type': 'G2', 'element': 'Pd', 'eta': 0.8}, {'type': 'G4', 'elements': [ 'Pd', 'Pd'], 'eta':0.2, 'gamma':0.3, 'zeta':1}, {'type': 'G4', 'elements': ['O', 'Pd'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}], 'Pd': [{'type': 'G2', 'element': 'Pd', 'eta': 0.2}, {'type': 'G4', 'elements': ['Pd', 'Pd'], 'eta':0.9, 'gamma':0.75, 'zeta':1.5}, {'type': 'G4', 'elements': ['O', 'Pd'], 'eta':0.4, 'gamma':0.3, 'zeta':4}], 'Cu': [{'type': 'G2', 'element': 'Cu', 'eta': 0.8}, {'type': 'G4', 'elements': ['Cu', 'O'], 'eta':0.2, 'gamma':0.3, 'zeta':1}, {'type': 'G4', 'elements': ['Cu', 'Cu'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}]} hiddenlayers = {'O': (2,), 'Pd': (2,), 'Cu': (2,)} weights = OrderedDict([('O', OrderedDict([(1, np.matrix([[-2.0, 6.0], [3.0, -3.0], [1.5, -0.9], [-2.5, -1.5]])), (2, np.matrix([[5.5], [3.6], [1.4]]))])), ('Pd', OrderedDict([(1, np.matrix([[-1.0, 3.0], [2.0, 4.2], [1.0, -0.7], [-3.0, 2.0]])), (2, np.matrix([[4.0], [0.5], [3.0]]))])), ('Cu', OrderedDict([(1, np.matrix([[0.0, 1.0], [-1.0, -2.0], [2.5, -1.9], [-3.5, 0.5]])), (2, np.matrix([[0.5], [1.6], [-1.4]]))]))]) scalings = OrderedDict([('O', OrderedDict([('intercept', -2.3), ('slope', 4.5)])), ('Pd', OrderedDict([('intercept', 1.6), ('slope', 2.5)])), ('Cu', OrderedDict([('intercept', -0.3), ('slope', -0.5)]))]) fingerprints_range = {"Cu": np.array([[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]]), "O": np.array([[0.2139617720858539, 2.258090276328769], [0.0, 1.085656080548734], [0.0, 0.0]]), "Pd": np.array([[0.0, 1.4751761770313006], [0.0, 0.28464992134267897], [0.0, 0.20167521020630502]])} # Testing pure-python and fortran versions of Gaussian-neural force call for fortran in [False, True]: for cores in range(1, 6): label = 'call-nonperiodic/%s-%i' % (fortran, cores) calc = Amp(descriptor=Gaussian(cutoff=6.5, Gs=Gs, fortran=fortran), model=NeuralNetwork(hiddenlayers=hiddenlayers, weights=weights, scalings=scalings, activation='sigmoid', fprange=fingerprints_range, mode='atom-centered', fortran=fortran), label=label, dblabel=label, cores=cores) predicted_energies = [calc.get_potential_energy(image) for image in images] for image_no in range(len(predicted_energies)): diff = abs(predicted_energies[image_no] - correct_energies[image_no]) assert (diff < 10.**(-15.)), \ 'The predicted energy of image %i is wrong!' % ( image_no + 1) predicted_forces = [calc.get_forces(image) for image in images] for image_no in range(len(predicted_forces)): for index in range(np.shape(predicted_forces[image_no])[0]): for direction in range( np.shape(predicted_forces[image_no])[1]): diff = abs(predicted_forces[image_no][index][ direction] - correct_forces[image_no][index][direction]) assert (diff < 10.**(-15.)), \ 'The predicted %i force of atom %i of image %i ' \ 'is wrong!' % (direction, index, image_no + 1) def periodic_test(): """Gaussian/Neural periodic standard. Checks that the answer matches that expected from previous Mathematica calculations. """ # Making the list of periodic images images = [Atoms(symbols='PdOPd', pbc=np.array([True, False, False], dtype=bool), cell=np.array( [[2., 0., 0.], [0., 2., 0.], [0., 0., 2.]]), positions=np.array( [[0.5, 1., 0.5], [1., 0.5, 1.], [1.5, 1.5, 1.5]])), Atoms(symbols='PdO', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[2., 0., 0.], [0., 2., 0.], [0., 0., 2.]]), positions=np.array( [[0.5, 1., 0.5], [1., 0.5, 1.]])), Atoms(symbols='Cu', pbc=np.array([True, True, False], dtype=bool), cell=np.array( [[1.8, 0., 0.], [0., 1.8, 0.], [0., 0., 1.8]]), positions=np.array( [[0., 0., 0.]]))] # Correct energies and forces correct_energies = [3.8560954326995978, 1.6120748520627273, 0.19433107801410093] correct_forces = \ [[[0.14747720528015523, -3.3010645563584973, 3.3008168318984463], [0.03333579762326405, 9.050780376599887, -0.42608278400777605], [-0.1808130029034193, -5.7497158202413905, -2.8747340478906698]], [[6.5035267996045045 * (10.**(-6.)), -6.503526799604495 * (10.**(-6.)), 0.00010834689201069249], [-6.5035267996045045 * (10.**(-6.)), 6.503526799604495 * (10.**(-6.)), -0.00010834689201069249]], [[0.0, 0.0, 0.0]]] # Parameters Gs = {'O': [{'type': 'G2', 'element': 'Pd', 'eta': 0.8}, {'type': 'G4', 'elements': ['O', 'Pd'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}], 'Pd': [{'type': 'G2', 'element': 'Pd', 'eta': 0.2}, {'type': 'G4', 'elements': ['Pd', 'Pd'], 'eta':0.9, 'gamma':0.75, 'zeta':1.5}], 'Cu': [{'type': 'G2', 'element': 'Cu', 'eta': 0.8}, {'type': 'G4', 'elements': ['Cu', 'Cu'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}]} hiddenlayers = {'O': (2,), 'Pd': (2,), 'Cu': (2,)} weights = OrderedDict([('O', OrderedDict([(1, np.matrix([[-2.0, 6.0], [3.0, -3.0], [1.5, -0.9]])), (2, np.matrix([[5.5], [3.6], [1.4]]))])), ('Pd', OrderedDict([(1, np.matrix([[-1.0, 3.0], [2.0, 4.2], [1.0, -0.7]])), (2, np.matrix([[4.0], [0.5], [3.0]]))])), ('Cu', OrderedDict([(1, np.matrix([[0.0, 1.0], [-1.0, -2.0], [2.5, -1.9]])), (2, np.matrix([[0.5], [1.6], [-1.4]]))]))]) scalings = OrderedDict([('O', OrderedDict([('intercept', -2.3), ('slope', 4.5)])), ('Pd', OrderedDict([('intercept', 1.6), ('slope', 2.5)])), ('Cu', OrderedDict([('intercept', -0.3), ('slope', -0.5)]))]) fingerprints_range = {"Cu": np.array([[2.8636310860653253, 2.8636310860653253], [1.5435994865298275, 1.5435994865298275]]), "O": np.array([[2.9409056366723028, 2.972494902604392], [1.9522542722823606, 4.0720361595017245]]), "Pd": np.array([[2.4629488092411096, 2.6160138774087125], [0.27127576524253594, 0.5898312261433813]])} # Testing pure-python and fortran versions of Gaussian-neural force call for fortran in [False, True]: for cores in range(1, 4): label = 'call-periodic/%s-%i' % (fortran, cores) calc = Amp(descriptor=Gaussian(cutoff=4., Gs=Gs, fortran=fortran), model=NeuralNetwork(hiddenlayers=hiddenlayers, weights=weights, scalings=scalings, activation='tanh', fprange=fingerprints_range, mode='atom-centered', fortran=fortran), label=label, dblabel=label, cores=cores) predicted_energies = [calc.get_potential_energy(image) for image in images] for image_no in range(len(predicted_energies)): diff = abs(predicted_energies[image_no] - correct_energies[image_no]) assert (diff < 10.**(-14.)), \ 'The predicted energy of image %i is wrong!' % ( image_no + 1) predicted_forces = [calc.get_forces(image) for image in images] for image_no in range(len(predicted_forces)): for index in range(np.shape(predicted_forces[image_no])[0]): for direction in range( np.shape(predicted_forces[image_no])[1]): diff = abs(predicted_forces[image_no][index][ direction] - correct_forces[image_no][index][direction]) assert (diff < 10.**(-11.)), \ 'The predicted %i force of atom %i of image' \ ' %i is wrong!' % (direction, index, image_no + 1) if __name__ == '__main__': non_periodic_test() periodic_test() amp-0.6/tests/test_gaussian_tflow.py0000644000175000017500000000521013137634440017617 0ustar muammarmuammar#!/usr/bin/env python """Simple test of the Amp calculator, using Gaussian descriptors and neural network model. Randomly generates data with the EMT potential in MD simulations.""" import sys from ase.calculators.emt import EMT from ase.lattice.surface import fcc110 from ase import Atoms, Atom from ase.md.velocitydistribution import MaxwellBoltzmannDistribution from ase import units from ase.md import VelocityVerlet from ase.constraints import FixAtoms from amp import Amp from amp.descriptor.gaussian import Gaussian def check_perform(): """Determines whether or not to perform the test. This should only perform the test if the python version is 2.x and tensorflow is installed. If returns False (meaning don't peform test), also supplies the reason.""" if sys.version_info >= (3,): return False, 'amp.model.tflow not supported in python3.' try: import tensorflow except ImportError: return False, 'Tensorflow not installed.' return True, '' def generate_data(count): """Generates test or training data with a simple MD simulation.""" atoms = fcc110('Pt', (2, 2, 2), vacuum=7.) adsorbate = Atoms([Atom('Cu', atoms[7].position + (0., 0., 2.5)), Atom('Cu', atoms[7].position + (0., 0., 5.))]) atoms.extend(adsorbate) atoms.set_constraint(FixAtoms(indices=[0, 2])) atoms.set_calculator(EMT()) MaxwellBoltzmannDistribution(atoms, 300. * units.kB) dyn = VelocityVerlet(atoms, dt=1. * units.fs) newatoms = atoms.copy() newatoms.set_calculator(EMT()) newatoms.get_potential_energy() images = [newatoms] for step in range(count - 1): dyn.run(50) newatoms = atoms.copy() newatoms.set_calculator(EMT()) newatoms.get_potential_energy() images.append(newatoms) return images def train_test(): """Gaussian/tflow train test.""" perform, reason = check_perform() if not perform: print('Skipping this test because {}.'.format(reason)) return from amp.model.tflow import NeuralNetwork label = 'train_test/calc' train_images = generate_data(2) convergence = { 'energy_rmse': 0.02, 'force_rmse': 0.02 } calc = Amp(descriptor=Gaussian(), model=NeuralNetwork(hiddenlayers=(3, 3), convergenceCriteria=convergence), label=label, cores=1) calc.train(images=train_images,) for image in train_images: print("energy =", calc.get_potential_energy(image)) print("forces =", calc.get_forces(image)) if __name__ == '__main__': train_test() amp-0.6/tests/misc_test/0002755000175000017500000000000013142667007015160 5ustar muammarmuammaramp-0.6/tests/misc_test/displaced_atom_test.py0000644000175000017500000001143213137634440021537 0ustar muammarmuammar""" An atom in an Atoms object is displaced a little bit and the potential energy both before and after displacement is calculated. The result should be different. """ ############################################################################### import numpy as np from ase import Atoms from collections import OrderedDict from amp import Amp from amp.descriptor.gaussian import Gaussian from amp.model.neuralnetwork import NeuralNetwork ############################################################################### def test(): """Displaced atom test.""" ########################################################################### # Parameters atoms = Atoms(symbols='PdOPd2', pbc=np.array([False, False, False], dtype=bool), cell=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]), positions=np.array( [[0., 1., 0.], [1., 2., 1.], [-1., 1., 2.], [1., 3., 2.]])) ########################################################################### # Parameters Gs = {'O': [{'type': 'G2', 'element': 'Pd', 'eta': 0.8}, {'type': 'G4', 'elements': [ 'Pd', 'Pd'], 'eta':0.2, 'gamma':0.3, 'zeta':1}, {'type': 'G4', 'elements': ['O', 'Pd'], 'eta':0.3, 'gamma':0.6, 'zeta':0.5}], 'Pd': [{'type': 'G2', 'element': 'Pd', 'eta': 0.2}, {'type': 'G4', 'elements': ['Pd', 'Pd'], 'eta':0.9, 'gamma':0.75, 'zeta':1.5}, {'type': 'G4', 'elements': ['O', 'Pd'], 'eta':0.4, 'gamma':0.3, 'zeta':4}]} hiddenlayers = {'O': (2,), 'Pd': (2,)} weights = OrderedDict([('O', OrderedDict([(1, np.matrix([[-2.0, 6.0], [3.0, -3.0], [1.5, -0.9], [-2.5, -1.5]])), (2, np.matrix([[5.5], [3.6], [1.4]]))])), ('Pd', OrderedDict([(1, np.matrix([[-1.0, 3.0], [2.0, 4.2], [1.0, -0.7], [-3.0, 2.0]])), (2, np.matrix([[4.0], [0.5], [3.0]]))]))]) scalings = OrderedDict([('O', OrderedDict([('intercept', -2.3), ('slope', 4.5)])), ('Pd', OrderedDict([('intercept', 1.6), ('slope', 2.5)]))]) fingerprints_range = {"O": np.array([[0.21396177208585404, 2.258090276328769], [0.0, 2.1579067008202975], [0.0, 0.0]]), "Pd": np.array([[0.0, 1.4751761770313006], [0.0, 0.697686078889583], [0.0, 0.37848964715610417]])} ########################################################################### calc = Amp(descriptor=Gaussian(cutoff=6.5, Gs=Gs, fortran=False,), model=NeuralNetwork(hiddenlayers=hiddenlayers, weights=weights, scalings=scalings, fprange=fingerprints_range, mode='atom-centered'), cores=1) atoms.set_calculator(calc) e1 = atoms.get_potential_energy(apply_constraint=False) e2 = calc.get_potential_energy(atoms) f1 = atoms.get_forces(apply_constraint=False) atoms[0].x += 0.5 boolean = atoms.calc.calculation_required(atoms, properties=['energy']) e3 = atoms.get_potential_energy(apply_constraint=False) e4 = calc.get_potential_energy(atoms) f2 = atoms.get_forces(apply_constraint=False) assert (e1 == e2 and e3 == e4 and abs(e1 - e3) > 10. ** (-3.) and (boolean is True) and (not (f1 == f2).all())), 'Displaced-atom test broken!' ############################################################################### if __name__ == '__main__': test() amp-0.6/tests/misc_test/rotated_atoms_test.py0000644000175000017500000000532413137634440021437 0ustar muammarmuammar#!/usr/bin/env python """This test checks rotation and translation invariance of descriptor schemes. Fingerprints both before and after a random rotation (+ translation) are calculated and compared.""" import numpy as np from numpy import sin, cos from ase import Atom, Atoms from amp.descriptor.gaussian import Gaussian from amp.utilities import hash_images import random def rotate_atom(x, y, z, phi, theta, psi): """Rotate atom in three dimensions.""" rotation_matrix = [ [cos(theta) * cos(psi), cos(phi) * sin(psi) + sin(phi) * sin(theta) * cos(psi), sin(phi) * sin(psi) - cos(phi) * sin(theta) * cos(psi)], [-cos(theta) * sin(psi), cos(phi) * cos(psi) - sin(phi) * sin(theta) * sin(psi), sin(phi) * cos(psi) + cos(phi) * sin(theta) * sin(psi)], [sin(theta), -sin(phi) * cos(theta), cos(phi) * cos(theta)] ] [[xprime], [yprime], [zprime]] = np.dot(rotation_matrix, [[x], [y], [z]]) return (xprime, yprime, zprime) def test(): """Rotational/translational invariance.""" for descriptor in [Gaussian(fortran=False), ]: # Non-rotated atomic configuration atoms = Atoms([Atom('Pt', (0., 0., 0.)), Atom('Pt', (0., 0., 1.)), Atom('Pt', (0., 2., 1.))]) images = hash_images([atoms], ordered=True) descriptor1 = descriptor descriptor1.calculate_fingerprints(images) fp1 = descriptor1.fingerprints[list(images.keys())[0]] # Randomly Rotated (and translated) atomic configuration rot = [random.random(), random.random(), random.random()] for i in range(1, len(atoms)): (atoms[i].x, atoms[i].y, atoms[i].z) = rotate_atom(atoms[i].x, atoms[i].y, atoms[i].z, rot[0] * np.pi, rot[1] * np.pi, rot[2] * np.pi) disp = [random.random(), random.random(), random.random()] for atom in atoms: atom.x += disp[0] atom.y += disp[1] atom.z += disp[2] images = hash_images([atoms], ordered=True) descriptor2 = descriptor descriptor2.calculate_fingerprints(images) fp2 = descriptor2.fingerprints[list(images.keys())[0]] for (element1, afp1), (element2, afp2) in zip(fp1, fp2): assert element1 == element2, 'rotated atoms test broken!' for _, __ in zip(afp1, afp2): assert (abs(_ - __) < 10 ** (-10.)), \ 'rotated atoms test broken!' if __name__ == '__main__': test() amp-0.6/tests/misc_test/numeric_analytic_test.py0000644000175000017500000001257513137634440022126 0ustar muammarmuammar#!/usr/bin/env python """This test randomly generates data with the EMT potential in MD simulations, and then checks for consistency between analytical and numerical forces, as well as dloss_dparameters.""" from ase.calculators.emt import EMT from ase.build import fcc110 from ase import Atoms, Atom from ase.md.velocitydistribution import MaxwellBoltzmannDistribution from ase import units from ase.md import VelocityVerlet from ase.constraints import FixAtoms from amp import Amp from amp.descriptor.gaussian import Gaussian from amp.model.neuralnetwork import NeuralNetwork from amp.model import LossFunction from amp.regression import Regressor def generate_data(count): """Generates test or training data with a simple MD simulation.""" atoms = fcc110('Pt', (2, 2, 1), vacuum=7.) adsorbate = Atoms([Atom('Cu', atoms[3].position + (0., 0., 2.5)), Atom('Cu', atoms[3].position + (0., 0., 5.))]) atoms.extend(adsorbate) atoms.set_constraint(FixAtoms(indices=[0, 2])) atoms.set_calculator(EMT()) MaxwellBoltzmannDistribution(atoms, 300. * units.kB) dyn = VelocityVerlet(atoms, dt=1. * units.fs) newatoms = atoms.copy() newatoms.set_calculator(EMT()) newatoms.get_potential_energy() images = [newatoms] for step in range(count - 1): dyn.run(50) newatoms = atoms.copy() newatoms.set_calculator(EMT()) newatoms.get_potential_energy() del newatoms.constraints # See ASE issue #64. images.append(newatoms) return images def test(): """Gaussian/Neural numeric-analytic consistency.""" images = generate_data(2) regressor = Regressor(optimizer='BFGS') calc = Amp(descriptor=Gaussian(), model=NeuralNetwork(hiddenlayers=(3, 3), regressor=regressor,), cores=1) step = 0 for d in [None, 0.00001]: for fortran in [True, False]: for cores in [1, 2]: step += 1 label = \ 'numeric_analytic_test/analytic-%s-%i' % (fortran, cores) \ if d is None \ else 'numeric_analytic_test/numeric-%s-%i' \ % (fortran, cores) print(label) loss = LossFunction(convergence={'energy_rmse': 10 ** 10, 'force_rmse': 10 ** 10}, d=d) calc.set_label(label) calc.dblabel = 'numeric_analytic_test/analytic-True-1' calc.model.lossfunction = loss calc.descriptor.fortran = fortran calc.model.fortran = fortran calc.cores = cores calc.train(images=images,) if step == 1: ref_energies = [] ref_forces = [] for image in images: ref_energies += [calc.get_potential_energy(image)] ref_forces += [calc.get_forces(image)] ref_dloss_dparameters = \ calc.model.lossfunction.dloss_dparameters else: energies = [] forces = [] for image in images: energies += [calc.get_potential_energy(image)] forces += [calc.get_forces(image)] dloss_dparameters = \ calc.model.lossfunction.dloss_dparameters for image_no in range(2): diff = abs(energies[image_no] - ref_energies[image_no]) assert (diff < 10.**(-13.)), \ 'The calculated value of energy of image %i is ' \ 'wrong!' % (image_no + 1) for atom_no in range(6): for i in range(3): diff = abs(forces[image_no][atom_no][i] - ref_forces[image_no][atom_no][i]) assert (diff < 10.**(-10.)), \ 'The calculated %i force of atom %i of ' \ 'image %i is wrong!' \ % (i, atom_no, image_no + 1) # Checks analytical and numerical dloss_dparameters for _ in range(len(ref_dloss_dparameters)): diff = abs(dloss_dparameters[_] - ref_dloss_dparameters[_]) assert(diff < 10 ** (-10.)), \ 'The calculated value of loss function ' \ 'derivative is wrong!' # Checks analytical and numerical forces forces = [] for image in images: image.set_calculator(calc) forces += [calc.calculate_numerical_forces(image, d=d)] for atom_no in range(6): for i in range(3): diff = abs(forces[image_no][atom_no][i] - ref_forces[image_no][atom_no][i]) print("diff =", diff) assert (diff < 10.**(-6.)), \ 'The calculated %i force of atom %i of ' \ 'image %i is wrong! (Diff = %f)' \ % (i, atom_no, image_no + 1, diff) if __name__ == '__main__': test() amp-0.6/tests/misc_test/fpplot_test.py0000644000175000017500000000416513137634440020100 0ustar muammarmuammar"""Simple test of the Amp calculator, using Gaussian descriptors and neural network model. Randomly generates data with the EMT potential in MD simulations.""" import matplotlib # The 'Agg' command must be *before* all other matplotlib imports for # headless operation. matplotlib.use('Agg') import os from ase import Atoms, Atom, units import ase.io from ase.calculators.emt import EMT from ase.build import fcc110 from ase.md.velocitydistribution import MaxwellBoltzmannDistribution from ase.md import VelocityVerlet from ase.constraints import FixAtoms from amp import Amp from amp.descriptor.gaussian import Gaussian from amp.model.neuralnetwork import NeuralNetwork from amp.model import LossFunction from amp.descriptor.analysis import FingerprintPlot def generate_data(count, filename='training.traj'): """Generates test or training data with a simple MD simulation.""" if os.path.exists(filename): return traj = ase.io.Trajectory(filename, 'w') atoms = fcc110('Pt', (2, 2, 2), vacuum=7.) atoms.extend(Atoms([Atom('Cu', atoms[7].position + (0., 0., 2.5)), Atom('Cu', atoms[7].position + (0., 0., 5.))])) atoms.set_constraint(FixAtoms(indices=[0, 2])) atoms.set_calculator(EMT()) atoms.get_potential_energy() traj.write(atoms) MaxwellBoltzmannDistribution(atoms, 300. * units.kB) dyn = VelocityVerlet(atoms, dt=1. * units.fs) for step in range(count - 1): dyn.run(50) traj.write(atoms) def test(): "FingerprintPlot test.""" generate_data(2, filename='fpplot-training.traj') calc = Amp(descriptor=Gaussian(), model=NeuralNetwork(), label='fpplot-test' ) calc.model.lossfunction = LossFunction(convergence={'energy_rmse': 1.00, 'force_rmse': 1.00}) calc.train(images='fpplot-training.traj') images = ase.io.Trajectory('fpplot-training.traj') fpplot = FingerprintPlot(calc) fpplot(images) fpplot(images, overlay=images[0]) fpplot(images, overlay=[images[1][2], images[0][-1]]) if __name__ == '__main__': test() amp-0.6/tests/test_gaussian_neural.py0000644000175000017500000000450713137634440017762 0ustar muammarmuammar#!/usr/bin/env python """Simple test of the Amp calculator, using Gaussian descriptors and neural network model. Randomly generates data with the EMT potential in MD simulations.""" from ase.calculators.emt import EMT from ase.build import fcc110 from ase import Atoms, Atom from ase.md.velocitydistribution import MaxwellBoltzmannDistribution from ase import units from ase.md import VelocityVerlet from ase.constraints import FixAtoms from amp import Amp from amp.descriptor.gaussian import Gaussian from amp.model.neuralnetwork import NeuralNetwork from amp.model import LossFunction def generate_data(count): """Generates test or training data with a simple MD simulation.""" atoms = fcc110('Pt', (2, 2, 2), vacuum=7.) adsorbate = Atoms([Atom('Cu', atoms[7].position + (0., 0., 2.5)), Atom('Cu', atoms[7].position + (0., 0., 5.))]) atoms.extend(adsorbate) atoms.set_constraint(FixAtoms(indices=[0, 2])) atoms.set_calculator(EMT()) MaxwellBoltzmannDistribution(atoms, 300. * units.kB) dyn = VelocityVerlet(atoms, dt=1. * units.fs) newatoms = atoms.copy() newatoms.set_calculator(EMT()) newatoms.get_potential_energy() images = [newatoms] for step in range(count - 1): dyn.run(50) newatoms = atoms.copy() newatoms.set_calculator(EMT()) newatoms.get_potential_energy() images.append(newatoms) return images def train_test(): """Gaussian/Neural train test.""" label = 'train_test/calc' train_images = generate_data(2) calc = Amp(descriptor=Gaussian(), model=NeuralNetwork(hiddenlayers=(3, 3)), label=label, cores=1) loss = LossFunction(convergence={'energy_rmse': 0.02, 'force_rmse': 0.02}) calc.model.lossfunction = loss calc.train(images=train_images,) for image in train_images: print("energy = %s" % str(calc.get_potential_energy(image))) print("forces = %s" % str(calc.get_forces(image))) # Test that we can re-load this calculator and call it again. del calc calc2 = Amp.load(label + '.amp') for image in train_images: print("energy = %s" % str(calc2.get_potential_energy(image))) print("forces = %s" % str(calc2.get_forces(image))) if __name__ == '__main__': train_test() amp-0.6/amp/0002755000175000017500000000000013140115602012564 5ustar muammarmuammaramp-0.6/amp/__init__.py0000644000175000017500000004261513137634440014717 0ustar muammarmuammarimport os import sys import shutil import numpy as np import tempfile import platform from getpass import getuser from socket import gethostname import subprocess import warnings import ase from ase.calculators.calculator import Calculator, Parameters try: from ase import __version__ as aseversion except ImportError: # We're on ASE 3.9 or older from ase.version import version as aseversion from .utilities import (make_filename, hash_images, Logger, string2dict, logo, now, assign_cores, TrainingConvergenceError, check_images) try: from amp import fmodules except ImportError: warnings.warn('Did not find fortran modules.') else: fmodules_version = 9 wrong_version = fmodules.check_version(version=fmodules_version) if wrong_version: raise RuntimeError('fortran modules are not updated. Recompile ' 'with f2py as described in the README. ' 'Correct version is %i.' % fmodules_version) _ampversion = '0.6' class Amp(Calculator, object): """Atomistic Machine-Learning Potential (Amp) ASE calculator Parameters ---------- descriptor : object Class representing local atomic environment. model : object Class representing the regression model. Can be only NeuralNetwork for now. Input arguments for NeuralNetwork are hiddenlayers, activation, weights, and scalings; for more information see docstring for the class NeuralNetwork. label : str Default prefix/location used for all files. dblabel : str Optional separate prefix/location for database files, including fingerprints, fingerprint derivatives, and neighborlists. This file location can be shared between calculator instances to avoid re-calculating redundant information. If not supplied, just uses the value from label. cores : int Can specify cores to use for parallel training; if None, will determine from environment envcommand : string For parallel processing across nodes, a command can be supplied here to load the appropriate environment before starting workers. logging : boolean Option to turn off logging; e.g., to speed up force calls. atoms : object ASE atoms objects with positions, symbols, energy, and forces in ASE format. """ implemented_properties = ['energy', 'forces'] def __init__(self, descriptor, model, label='amp', dblabel=None, cores=None, envcommand=None, logging=True, atoms=None): self.logging = logging Calculator.__init__(self, label=label, atoms=atoms) # Note self._log is set and self._printheader is called by above # call when it runs self.set_label. self._parallel = {'envcommand': envcommand} # Note the following are properties: these are setter functions. self.descriptor = descriptor self.model = model self.cores = cores # Note this calls 'assign_cores'. self.dblabel = label if dblabel is None else dblabel @property def cores(self): """ Get or set the cores for the parallel environment. Parameters ---------- cores : int or dictionary Parallel configuration. If cores is an integer, parallelizes over this many processes on machine localhost. cores can also be a dictionary of the type {'node324': 16, 'node325': 16}. If not specified, tries to determine from environment, using amp.utilities.assign_cores. """ return self._parallel['cores'] @cores.setter def cores(self, cores): self._parallel['cores'] = assign_cores(cores, log=self._log) @property def descriptor(self): """ Get or set the atomic descriptor. Parameters ---------- descriptor : object Class instance representing the local atomic environment. """ return self._descriptor @descriptor.setter def descriptor(self, descriptor): descriptor.parent = self # gives the descriptor object a reference to # the main Amp instance. Then descriptor can pull parameters directly # from Amp without needing them to be passed in each method call. self._descriptor = descriptor self.reset() # Clears any old calculations. @property def model(self): """ Get or set the machine-learning model. Parameters ---------- model : object Class instance representing the regression model. """ return self._model @model.setter def model(self, model): model.parent = self # gives the model object a reference to the main # Amp instance. Then model can pull parameters directly from Amp # without needing them to be passed in each method call. self._model = model self.reset() # Clears any old calculations. @classmethod def load(Cls, file, Descriptor=None, Model=None, **kwargs): """Attempts to load calculators and return a new instance of Amp. Only a filename or file-like object is required, in typical cases. If using a home-rolled descriptor or model, also supply uninstantiated classes to those models, as in Model=MyModel. (Not as Model=MyModel()!) Any additional keyword arguments (such as label or dblabel) can be fed through to Amp. Parameters ---------- file : str Name of the file to load data from. Descriptor : object Class representing local atomic environment. Model : object Class representing the regression model. """ if hasattr(file, 'read'): text = file.read() else: with open(file) as f: text = f.read() # Unpack parameter dictionaries. p = string2dict(text) for key in ['descriptor', 'model']: p[key] = string2dict(p[key]) # If modules are not specified, find them. if Descriptor is None: Descriptor = importhelper(p['descriptor'].pop('importname')) if Model is None: Model = importhelper(p['model'].pop('importname')) # Key 'importname' and the value removed so that it is not splatted # into the keyword arguments used to instantiate in the next line. # Instantiate the descriptor and model. descriptor = Descriptor(**p['descriptor']) # ** sends all the key-value pairs at once. model = Model(**p['model']) # Instantiate Amp. calc = Cls(descriptor=descriptor, model=model, **kwargs) calc._log('Loaded file: %s' % file) return calc def set(self, **kwargs): """Function to set parameters. For now, this doesn't do anything as all parameters are within the model and descriptor. """ changed_parameters = Calculator.set(self, **kwargs) if len(changed_parameters) > 0: self.reset() def set_label(self, label): """Sets label, ensuring that any needed directories are made. Parameters ---------- label : str Default prefix/location used for all files. """ Calculator.set_label(self, label) # Create directories for output structure if needed. # Note ASE doesn't do this for us. if self.label: if (self.directory != os.curdir and not os.path.isdir(self.directory)): os.makedirs(self.directory) if self.logging is True: self._log = Logger(make_filename(self.label, '-log.txt')) else: self._log = Logger(None) self._printheader(self._log) def calculate(self, atoms, properties, system_changes): """Calculation of the energy of system and forces of all atoms. """ # The inherited method below just sets the atoms object, # if specified, to self.atoms. Calculator.calculate(self, atoms, properties, system_changes) log = self._log log('Calculation requested.') images = hash_images([self.atoms]) key = list(images.keys())[0] if properties == ['energy']: log('Calculating potential energy...', tic='pot-energy') self.descriptor.calculate_fingerprints(images=images, log=log, calculate_derivatives=False) energy = self.model.calculate_energy( self.descriptor.fingerprints[key]) self.results['energy'] = energy log('...potential energy calculated.', toc='pot-energy') if properties == ['forces']: log('Calculating forces...', tic='forces') self.descriptor.calculate_fingerprints(images=images, log=log, calculate_derivatives=True) forces = \ self.model.calculate_forces( self.descriptor.fingerprints[key], self.descriptor.fingerprintprimes[key]) self.results['forces'] = forces log('...forces calculated.', toc='forces') def train(self, images, overwrite=False, ): """Fits the model to the training images. Parameters ---------- images : list or str List of ASE atoms objects with positions, symbols, energies, and forces in ASE format. This is the training set of data. This can also be the path to an ASE trajectory (.traj) or database (.db) file. Energies can be obtained from any reference, e.g. DFT calculations. overwrite : bool If an output file with the same name exists, overwrite it. """ log = self._log log('\nAmp training started. ' + now() + '\n') log('Descriptor: %s\n (%s)' % (self.descriptor.__class__.__name__, self.descriptor)) log('Model: %s\n (%s)' % (self.model.__class__.__name__, self.model)) images = hash_images(images, log=log) log('\nDescriptor\n==========') train_forces = self.model.forcetraining # True / False check_images(images, forces=train_forces) self.descriptor.calculate_fingerprints( images=images, parallel=self._parallel, log=log, calculate_derivatives=train_forces) log('\nModel fitting\n=============') result = self.model.fit(trainingimages=images, descriptor=self.descriptor, log=log, parallel=self._parallel) if result is True: log('Amp successfully trained. Saving current parameters.') filename = self.label + '.amp' else: log('Amp not trained successfully. Saving current parameters.') filename = make_filename(self.label, '-untrained-parameters.amp') filename = self.save(filename, overwrite) log('Parameters saved in file "%s".' % filename) log("This file can be opened with `calc = Amp.load('%s')`" % filename) if result is False: raise TrainingConvergenceError('Amp did not converge upon ' 'training. See log file for' ' more information.') def save(self, filename, overwrite=False): """Saves the calculator in a way that it can be re-opened with load. Parameters ---------- filename : str File object or path to the file to write to. overwrite : bool If an output file with the same name exists, overwrite it. """ if os.path.exists(filename): if overwrite is False: oldfilename = filename filename = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.amp').name self._log('File "%s" exists. Instead saving to "%s".' % (oldfilename, filename)) else: oldfilename = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.amp').name self._log('Overwriting file: "%s". Moving original to "%s".' % (filename, oldfilename)) shutil.move(filename, oldfilename) descriptor = self.descriptor.tostring() model = self.model.tostring() p = Parameters({'descriptor': descriptor, 'model': model}) p.write(filename) return filename def _printheader(self, log): """Prints header to log file; inspired by that in GPAW. """ log(logo) log('Amp: Atomistic Machine-learning Package') log('Developed by Andrew Peterson, Alireza Khorshidi, and others,') log('Brown University.') log('PI Website: http://brown.edu/go/catalyst') log('Official repository: http://bitbucket.org/andrewpeterson/amp') log('Official documentation: http://amp.readthedocs.org/') log('Citation:') log(' Alireza Khorshidi & Andrew A. Peterson,') log(' Computer Physics Communications 207: 310-324 (2016).') log(' http://doi.org/10.1016/j.cpc.2016.05.010') log('=' * 70) log('User: %s' % getuser()) log('Hostname: %s' % gethostname()) log('Date: %s' % now(with_utc=True)) uname = platform.uname() log('Architecture: %s' % uname[4]) log('PID: %s' % os.getpid()) log('Amp version: %s' % _ampversion) ampdirectory = os.path.dirname(os.path.abspath(__file__)) log('Amp directory: %s' % ampdirectory) commithash, commitdate = get_git_commit(ampdirectory) log(' Last commit: %s' % commithash) log(' Last commit date: %s' % commitdate) log('Python: v{0}.{1}.{2}: %s'.format(*sys.version_info[:3]) % sys.executable) log('ASE v%s: %s' % (aseversion, os.path.dirname(ase.__file__))) log('NumPy v%s: %s' % (np.version.version, os.path.dirname(np.__file__))) # SciPy is not a strict dependency. try: import scipy log('SciPy v%s: %s' % (scipy.version.version, os.path.dirname(scipy.__file__))) except ImportError: log('SciPy: not available') # ZMQ an pxssh are only necessary for parallel calculations. try: import zmq log('ZMQ/PyZMQ v%s/v%s: %s' % (zmq.zmq_version(), zmq.pyzmq_version(), os.path.dirname(zmq.__file__))) except ImportError: log('ZMQ: not available') try: import pxssh log('pxssh: %s' % os.path.dirname(pxssh.__file__)) except ImportError: log('pxssh: Not available from pxssh.') try: from pexpect import pxssh except ImportError: log('pxssh: Not available from pexpect.') else: import pexpect log('pxssh (via pexpect v%s): %s' % (pexpect.__version__, pxssh.__file__)) log('=' * 70) def importhelper(importname): """Manually compiled list of available modules. This is to prevent the execution of arbitrary (potentially malicious) code. However, since there is an `eval` statement in string2dict maybe this is silly. """ if importname == '.descriptor.gaussian.Gaussian': from .descriptor.gaussian import Gaussian as Module elif importname == '.descriptor.zernike.Zernike': from .descriptor.zernike import Zernike as Module elif importname == '.descriptor.bispectrum.Bispectrum': from .descriptor.bispectrum import Bispectrum as Module elif importname == '.model.neuralnetwork.NeuralNetwork': from .model.neuralnetwork import NeuralNetwork as Module elif importname == '.model.neuralnetwork.tflow': from .model.tflow import NeuralNetwork as Module elif importname == '.model.LossFunction': from .model import LossFunction as Module else: raise NotImplementedError( 'Attempt to import the module %s. Was this intended? ' 'If so, trying manually importing this module and ' 'feeding it to Amp.load. To avoid this error, this ' 'module can be added to amp.importhelper.' % importname) return Module def get_git_commit(ampdirectory): """Attempts to get the last git commit from the amp directory. """ pwd = os.getcwd() os.chdir(ampdirectory) try: with open(os.devnull, 'w') as devnull: output = subprocess.check_output(['git', 'log', '-1', '--pretty=%H\t%ci'], stderr=devnull) except: output = 'unknown hash\tunknown date' output = output.strip() commithash, commitdate = output.split(b'\t') os.chdir(pwd) return commithash, commitdate amp-0.6/amp/regression/0002755000175000017500000000000013140115602014744 5ustar muammarmuammaramp-0.6/amp/regression/__init__.py0000644000175000017500000001020613137634440017066 0ustar muammarmuammarfrom ..utilities import ConvergenceOccurred class Regressor: """Class to manage the regression of a generic model. That is, for a given parameter set, calculates the cost function (the difference in predicted energies and actual energies across training images), then decides how to adjust the parameters to reduce this cost function. Global optimization conditioners (e.g., simulated annealing, etc.) can be built into this class. Parameters ---------- optimizer : str The optimizer to use. Several defaults are available including 'L-BFGS-B', 'BFGS', 'TNC', or 'NCG'. Alternatively, any function can be supplied which behaves like scipy.optimize.fmin_bfgs. optimizer_kwargs : dict Optional keywords for the corresponding optimizer. lossprime : boolean Decides whether or not the regressor needs to be fed in by gradient of the loss function as well as the loss function itself. """ def __init__(self, optimizer='BFGS', optimizer_kwargs=None, lossprime=True): """optimizer can be specified; it should behave like a scipy.optimize optimizer. That is, it should take as its first two arguments the function to be optimized and the initial guess of the optimal paramters. Additional keyword arguments can be fed through the optimizer_kwargs dictionary.""" user_kwargs = optimizer_kwargs optimizer_kwargs = {} if optimizer == 'BFGS': from scipy.optimize import fmin_bfgs as optimizer optimizer_kwargs = {'gtol': 1e-15, } elif optimizer == 'L-BFGS-B': from scipy.optimize import fmin_l_bfgs_b as optimizer optimizer_kwargs = {'factr': 1e+02, 'pgtol': 1e-08, 'maxfun': 1000000, 'maxiter': 1000000} import scipy from distutils.version import StrictVersion if StrictVersion(scipy.__version__) >= StrictVersion('0.17.0'): optimizer_kwargs['maxls'] = 2000 elif optimizer == 'TNC': from scipy.optimize import fmin_tnc as optimizer optimizer_kwargs = {'ftol': 0., 'xtol': 0., 'pgtol': 1e-08, 'maxfun': 1000000, } elif optimizer == 'NCG': from scipy.optimize import fmin_ncg as optimizer optimizer_kwargs = {'avextol': 1e-15, } if user_kwargs: optimizer_kwargs.update(user_kwargs) self.optimizer = optimizer self.optimizer_kwargs = optimizer_kwargs self.lossprime = lossprime def regress(self, model, log): """Performs the regression. Calls model.get_loss, which should return the current value of the loss function until convergence has been reached, at which point it should raise a amp.utilities.ConvergenceException. Parameters ---------- model : object Class representing the regression model. log : str Name of script to log progress. """ log('Starting parameter optimization.', tic='opt') log(' Optimizer: %s' % self.optimizer) log(' Optimizer kwargs: %s' % self.optimizer_kwargs) x0 = model.vector.copy() try: if self.lossprime: self.optimizer(model.get_loss, x0, model.get_lossprime, **self.optimizer_kwargs) else: self.optimizer(model.get_loss, x0, **self.optimizer_kwargs) except ConvergenceOccurred: log('...optimization successful.', toc='opt') return True else: log('...optimization unsuccessful.', toc='opt') if self.lossprime: max_lossprime = \ max(abs(max(model.lossfunction.dloss_dparameters)), abs(min(model.lossfunction.dloss_dparameters))) log('...maximum absolute value of loss prime: %.3e' % max_lossprime) return False amp-0.6/amp/model.f900000644000175000017500000010261513137634440014223 0ustar muammarmuammar!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Fortran Version = 9 subroutine check_version(version, warning) implicit none integer:: version, warning !f2py intent(in):: version !f2py intent(out):: warning if (version .NE. 9) then warning = 1 else warning = 0 end if end subroutine !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! module containing all the data of fingerprints (should be fed in ! by python) module fingerprint_props implicit none integer, allocatable:: num_fingerprints_of_elements(:) double precision, allocatable:: raveled_fingerprints(:, :) double precision, allocatable:: raveled_fingerprintprimes(:, :) end module fingerprint_props !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! module containing model data (should be fed in by python) module model_props implicit none ! mode_signal is 1 for image-centered mode, and 2 for ! atom-centered mode integer:: mode_signal logical:: train_forces double precision:: energy_coefficient double precision:: force_coefficient double precision:: overfit logical:: numericprime double precision:: d end module model_props !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! module containing all the data of images (should be fed in by ! python) module images_props implicit none integer:: num_images ! atom-centered variables integer:: num_elements integer, allocatable:: elements_numbers(:) integer, allocatable:: num_images_atoms(:) integer, allocatable:: atomic_numbers(:) integer, allocatable:: num_neighbors(:) integer, allocatable:: raveled_neighborlists(:) double precision, allocatable:: actual_energies(:) double precision, allocatable:: actual_forces(:, :) ! image-centered variables integer:: num_atoms double precision, allocatable:: atomic_positions(:, :) end module images_props !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! subroutine that calculates the loss function and its prime subroutine calculate_loss(parameters, num_parameters, & lossprime, loss, dloss_dparameters, energyloss, forceloss, & energy_maxresid, force_maxresid) use images_props use fingerprint_props use model_props use neuralnetwork !!!!!!!!!!!!!!!!!!!!!!!! input/output variables !!!!!!!!!!!!!!!!!!!!!!!! integer:: num_parameters double precision:: parameters(num_parameters) logical:: lossprime double precision:: loss, energyloss, forceloss double precision:: energy_maxresid, force_maxresid double precision:: dloss_dparameters(num_parameters) !f2py intent(in):: parameters, num_parameters !f2py intent(in):: lossprime !f2py intent(out):: loss, energyloss, forceloss !f2py intent(out):: energy_maxresid, force_maxresid !f2py intent(out):: dloss_dparameters !!!!!!!!!!!!!!!!!!!!!!!!!!! type definition !!!!!!!!!!!!!!!!!!!!!!!!!!!! type:: image_forces sequence double precision, allocatable:: atom_forces(:, :) end type image_forces type:: integer_one_d_array sequence integer, allocatable:: onedarray(:) end type integer_one_d_array type:: embedded_real_one_one_d_array sequence type(real_one_d_array), allocatable:: onedarray(:) end type embedded_real_one_one_d_array type:: embedded_real_one_two_d_array sequence type(real_two_d_array), allocatable:: onedarray(:) end type embedded_real_one_two_d_array type:: embedded_integer_one_one_d_array sequence type(integer_one_d_array), allocatable:: onedarray(:) end type embedded_integer_one_one_d_array type:: embedded_one_one_two_d_array sequence type(embedded_real_one_two_d_array), allocatable:: onedarray(:) end type embedded_one_one_two_d_array !!!!!!!!!!!!!!!!!!!!!!!!!! dummy variables !!!!!!!!!!!!!!!!!!!!!!!!!!!!! double precision, allocatable:: fingerprint(:) type(embedded_real_one_one_d_array), allocatable:: & unraveled_fingerprints(:) type(integer_one_d_array), allocatable:: & unraveled_atomic_numbers(:) double precision:: amp_energy, actual_energy, atom_energy double precision:: residual_per_atom, dforce, force_resid double precision:: overfitloss integer:: i, index, j, p, k, q, l, m, & len_of_fingerprint, symbol, element, image_no, num_inputs double precision:: denergy_dparameters(num_parameters) double precision:: daenergy_dparameters(num_parameters) double precision:: dforce_dparameters(num_parameters) double precision:: doverfitloss_dparameters(num_parameters) type(real_two_d_array), allocatable:: dforces_dparameters(:) type(image_forces), allocatable:: unraveled_actual_forces(:) type(embedded_integer_one_one_d_array), allocatable:: & unraveled_neighborlists(:) type(embedded_one_one_two_d_array), allocatable:: & unraveled_fingerprintprimes(:) double precision, allocatable:: fingerprintprime(:) integer:: nindex, nsymbol, selfindex double precision, allocatable:: & actual_forces_(:, :), amp_forces(:, :) integer, allocatable:: neighborindices(:) ! image-centered mode type(real_one_d_array), allocatable:: & unraveled_atomic_positions(:) double precision, allocatable:: inputs(:), inputs_(:) !!!!!!!!!!!!!!!!!!!!!!!!!!!! calculations !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! if (mode_signal == 1) then allocate(inputs(3 * num_atoms)) allocate(inputs_(3 * num_atoms)) allocate(unraveled_atomic_positions(num_images)) call unravel_atomic_positions() else allocate(unraveled_fingerprints(num_images)) allocate(unraveled_atomic_numbers(num_images)) allocate(unraveled_neighborlists(num_images)) allocate(unraveled_fingerprintprimes(num_images)) call unravel_atomic_numbers() call unravel_fingerprints() end if if (train_forces .EQV. .TRUE.) then allocate(unraveled_actual_forces(num_images)) call unravel_actual_forces() if (mode_signal == 2) then call unravel_neighborlists() call unravel_fingerprintprimes() end if end if energyloss = 0.0d0 forceloss = 0.0d0 energy_maxresid = 0.0d0 force_maxresid = 0.0d0 do j = 1, num_parameters dloss_dparameters(j) = 0.0d0 end do ! summation over images do image_no = 1, num_images if (mode_signal == 1) then num_inputs = 3 * num_atoms inputs = unraveled_atomic_positions(image_no)%onedarray else num_atoms = num_images_atoms(image_no) end if actual_energy = actual_energies(image_no) ! calculates amp_energy call calculate_energy(image_no) ! calculates energy_maxresid residual_per_atom = ABS(amp_energy - actual_energy) / num_atoms if (residual_per_atom .GT. energy_maxresid) then energy_maxresid = residual_per_atom end if ! calculates energyloss energyloss = energyloss + residual_per_atom ** 2.0d0 if (lossprime .EQV. .TRUE.) then ! calculates denergy_dparameters if (mode_signal == 1) then ! image-centered mode denergy_dparameters = & calculate_denergy_dparameters_(num_inputs, inputs, & num_parameters, parameters) else ! atom-centered mode do j = 1, num_parameters denergy_dparameters(j) = 0.0d0 end do if (numericprime .EQV. .FALSE.) then call calculate_denergy_dparameters(image_no) else call calculate_numerical_denergy_dparameters(image_no) end if end if ! calculates contribution of energyloss to dloss_dparameters do j = 1, num_parameters dloss_dparameters(j) = dloss_dparameters(j) + & energy_coefficient * 2.0d0 * & (amp_energy - actual_energy) * & denergy_dparameters(j) / (num_atoms ** 2.0d0) end do end if if (train_forces .EQV. .TRUE.) then allocate(actual_forces_(num_atoms, 3)) do selfindex = 1, num_atoms do i = 1, 3 actual_forces_(selfindex, i) = & unraveled_actual_forces(& image_no)%atom_forces(selfindex, i) end do end do ! calculates amp_forces call calculate_forces(image_no) ! calculates forceloss do selfindex = 1, num_atoms do i = 1, 3 forceloss = forceloss + & (1.0d0 / 3.0d0) * (amp_forces(selfindex, i) - & actual_forces_(selfindex, i)) ** 2.0d0 / num_atoms end do end do ! calculates force_maxresid do selfindex = 1, num_atoms do i = 1, 3 force_resid = & ABS(amp_forces(selfindex, i) - & actual_forces_(selfindex, i)) if (force_resid .GT. force_maxresid) then force_maxresid = force_resid end if end do end do if (lossprime .EQV. .TRUE.) then allocate(dforces_dparameters(num_atoms)) do selfindex = 1, num_atoms allocate(dforces_dparameters(& selfindex)%twodarray(3, num_parameters)) do i = 1, 3 do j = 1, num_parameters dforces_dparameters(& selfindex)%twodarray(i, j) = 0.0d0 end do end do end do ! calculates dforces_dparameters if (numericprime .EQV. .FALSE.) then call calculate_dforces_dparameters(image_no) else call calculate_numerical_dforces_dparameters(image_no) end if ! calculates contribution of forceloss to ! dloss_dparameters do selfindex = 1, num_atoms do i = 1, 3 do j = 1, num_parameters dloss_dparameters(j) = & dloss_dparameters(j) + & force_coefficient * (2.0d0 / 3.0d0) * & (amp_forces(selfindex, i) - & actual_forces_(selfindex, i)) * & dforces_dparameters(& selfindex)%twodarray(i, j) / num_atoms end do end do end do do p = 1, size(dforces_dparameters) deallocate(dforces_dparameters(p)%twodarray) end do deallocate(dforces_dparameters) end if deallocate(actual_forces_) deallocate(amp_forces) end if end do loss = energy_coefficient * energyloss + & force_coefficient * forceloss ! if overfit coefficient is more than zero, overfit ! contribution to loss and dloss_dparameters is also added. if (overfit .GT. 0.0d0) then overfitloss = 0.0d0 do j = 1, num_parameters overfitloss = overfitloss + & parameters(j) ** 2.0d0 end do overfitloss = overfit * overfitloss loss = loss + overfitloss do j = 1, num_parameters doverfitloss_dparameters(j) = & 2.0d0 * overfit * parameters(j) dloss_dparameters(j) = dloss_dparameters(j) + & doverfitloss_dparameters(j) end do end if ! deallocations for all images if (mode_signal == 1) then do image_no = 1, num_images deallocate(unraveled_atomic_positions(image_no)%onedarray) end do deallocate(unraveled_atomic_positions) deallocate(inputs) deallocate(inputs_) else do image_no = 1, num_images deallocate(unraveled_atomic_numbers(image_no)%onedarray) end do deallocate(unraveled_atomic_numbers) do image_no = 1, num_images num_atoms = num_images_atoms(image_no) do index = 1, num_atoms deallocate(unraveled_fingerprints(& image_no)%onedarray(index)%onedarray) end do deallocate(unraveled_fingerprints(image_no)%onedarray) end do deallocate(unraveled_fingerprints) end if if (train_forces .EQV. .TRUE.) then do image_no = 1, num_images deallocate(unraveled_actual_forces(image_no)%atom_forces) end do deallocate(unraveled_actual_forces) if (mode_signal == 2) then do image_no = 1, num_images num_atoms = num_images_atoms(image_no) do selfindex = 1, num_atoms do nindex = 1, & size(unraveled_fingerprintprimes(& image_no)%onedarray(selfindex)%onedarray) deallocate(& unraveled_fingerprintprimes(& image_no)%onedarray(selfindex)%onedarray(& nindex)%twodarray) end do deallocate(unraveled_fingerprintprimes(& image_no)%onedarray(selfindex)%onedarray) end do deallocate(unraveled_fingerprintprimes(& image_no)%onedarray) end do deallocate(unraveled_fingerprintprimes) do image_no = 1, num_images num_atoms = num_images_atoms(image_no) do index = 1, num_atoms deallocate(unraveled_neighborlists(& image_no)%onedarray(index)%onedarray) end do deallocate(unraveled_neighborlists(image_no)%onedarray) end do deallocate(unraveled_neighborlists) end if end if contains !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! calculates amp_energy subroutine calculate_energy(image_no) if (mode_signal == 1) then amp_energy = & calculate_image_energy(num_inputs, inputs, num_parameters, & parameters) else amp_energy = 0.0d0 do index = 1, num_atoms symbol = unraveled_atomic_numbers(& image_no)%onedarray(index) do element = 1, num_elements if (symbol == elements_numbers(element)) then exit end if end do len_of_fingerprint = num_fingerprints_of_elements(element) allocate(fingerprint(len_of_fingerprint)) do p = 1, len_of_fingerprint fingerprint(p) = & unraveled_fingerprints(& image_no)%onedarray(index)%onedarray(p) end do atom_energy = calculate_atomic_energy(symbol, & len_of_fingerprint, fingerprint, num_elements, & elements_numbers, num_parameters, parameters) deallocate(fingerprint) amp_energy = amp_energy + atom_energy end do end if end subroutine calculate_energy !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! calculates amp_forces subroutine calculate_forces(image_no) allocate(amp_forces(num_atoms, 3)) do selfindex = 1, num_atoms do i = 1, 3 amp_forces(selfindex, i) = 0.0d0 end do end do do selfindex = 1, num_atoms if (mode_signal == 1) then do i = 1, 3 do p = 1, 3 * num_atoms inputs_(p) = 0.0d0 end do inputs_(3 * (selfindex - 1) + i) = 1.0d0 amp_forces(selfindex, i) = calculate_force_(num_inputs, & inputs, inputs_, num_parameters, parameters) end do else ! neighborindices list is generated. allocate(neighborindices(size(& unraveled_neighborlists(image_no)%onedarray(& selfindex)%onedarray))) do p = 1, size(unraveled_neighborlists(& image_no)%onedarray(selfindex)%onedarray) neighborindices(p) = unraveled_neighborlists(& image_no)%onedarray(selfindex)%onedarray(p) end do do l = 1, size(neighborindices) nindex = neighborindices(l) nsymbol = unraveled_atomic_numbers(& image_no)%onedarray(nindex) do element = 1, num_elements if (nsymbol == elements_numbers(element)) then exit end if end do len_of_fingerprint = & num_fingerprints_of_elements(element) allocate(fingerprint(len_of_fingerprint)) do p = 1, len_of_fingerprint fingerprint(p) = unraveled_fingerprints(& image_no)%onedarray(nindex)%onedarray(p) end do do i = 1, 3 allocate(fingerprintprime(len_of_fingerprint)) do p = 1, len_of_fingerprint fingerprintprime(p) = & unraveled_fingerprintprimes(& image_no)%onedarray(& selfindex)%onedarray(l)%twodarray(i, p) end do dforce = calculate_force(nsymbol, len_of_fingerprint, & fingerprint, fingerprintprime, & num_elements, elements_numbers, & num_parameters, parameters) amp_forces(selfindex, i) = & amp_forces(selfindex, i) + dforce deallocate(fingerprintprime) end do deallocate(fingerprint) end do deallocate(neighborindices) end if end do end subroutine calculate_forces !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! calculates analytical denergy_dparameters in ! the atom-centered mode. subroutine calculate_denergy_dparameters(image_no) do index = 1, num_atoms symbol = unraveled_atomic_numbers(image_no)%onedarray(index) do element = 1, num_elements if (symbol == elements_numbers(element)) then exit end if end do len_of_fingerprint = num_fingerprints_of_elements(element) allocate(fingerprint(len_of_fingerprint)) do p = 1, len_of_fingerprint fingerprint(p) = unraveled_fingerprints(& image_no)%onedarray(index)%onedarray(p) end do daenergy_dparameters = calculate_datomicenergy_dparameters(& symbol, len_of_fingerprint, fingerprint, & num_elements, elements_numbers, num_parameters, parameters) deallocate(fingerprint) do j = 1, num_parameters denergy_dparameters(j) = denergy_dparameters(j) + & daenergy_dparameters(j) end do end do end subroutine calculate_denergy_dparameters !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! calculates numerical denergy_dparameters in the ! atom-centered mode. subroutine calculate_numerical_denergy_dparameters(image_no) double precision:: eplus, eminus do j = 1, num_parameters parameters(j) = parameters(j) + d call calculate_energy(image_no) eplus = amp_energy parameters(j) = parameters(j) - 2.0d0 * d call calculate_energy(image_no) eminus = amp_energy denergy_dparameters(j) = (eplus - eminus) / (2.0d0 * d) parameters(j) = parameters(j) + d end do call calculate_energy(image_no) end subroutine calculate_numerical_denergy_dparameters !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! calculates dforces_dparameters. subroutine calculate_dforces_dparameters(image_no) if (mode_signal == 1) then ! image-centered mode do selfindex = 1, num_atoms do i = 1, 3 do p = 1, 3 * num_atoms inputs_(p) = 0.0d0 end do inputs_(3 * (selfindex - 1) + i) = 1.0d0 dforce_dparameters = calculate_dforce_dparameters_(& num_inputs, inputs, inputs_, num_parameters, parameters) do j = 1, num_parameters dforces_dparameters(selfindex)%twodarray(i, j) = & dforce_dparameters(j) end do end do end do else ! atom-centered mode do selfindex = 1, num_atoms ! neighborindices list is generated. allocate(neighborindices(size(& unraveled_neighborlists(image_no)%onedarray(& selfindex)%onedarray))) do p = 1, size(unraveled_neighborlists(& image_no)%onedarray(selfindex)%onedarray) neighborindices(p) = unraveled_neighborlists(& image_no)%onedarray(selfindex)%onedarray(p) end do do l = 1, size(neighborindices) nindex = neighborindices(l) nsymbol = unraveled_atomic_numbers(& image_no)%onedarray(nindex) do element = 1, num_elements if (nsymbol == elements_numbers(element)) then exit end if end do len_of_fingerprint = & num_fingerprints_of_elements(element) allocate(fingerprint(len_of_fingerprint)) do p = 1, len_of_fingerprint fingerprint(p) = unraveled_fingerprints(& image_no)%onedarray(nindex)%onedarray(p) end do do i = 1, 3 allocate(fingerprintprime(len_of_fingerprint)) do p = 1, len_of_fingerprint fingerprintprime(p) = & unraveled_fingerprintprimes(& image_no)%onedarray(selfindex)%onedarray(& l)%twodarray(i, p) end do dforce_dparameters = calculate_dforce_dparameters(& nsymbol, len_of_fingerprint, fingerprint, & fingerprintprime, num_elements, & elements_numbers, num_parameters, parameters) deallocate(fingerprintprime) do j = 1, num_parameters dforces_dparameters(& selfindex)%twodarray(i, j) = & dforces_dparameters(& selfindex)%twodarray(i, j) + & dforce_dparameters(j) end do end do deallocate(fingerprint) end do deallocate(neighborindices) end do end if end subroutine calculate_dforces_dparameters !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! calculates numerical dforces_dparameters in the ! atom-centered mode. subroutine calculate_numerical_dforces_dparameters(image_no) double precision, allocatable:: fplus(:, :), fminus(:, :) do j = 1, num_parameters parameters(j) = parameters(j) + d deallocate(amp_forces) call calculate_forces(image_no) allocate(fplus(num_atoms, 3)) do selfindex = 1, num_atoms do i = 1, 3 fplus(selfindex, i) = amp_forces(selfindex, i) end do end do parameters(j) = parameters(j) - 2.0d0 * d deallocate(amp_forces) call calculate_forces(image_no) allocate(fminus(num_atoms, 3)) do selfindex = 1, num_atoms do i = 1, 3 fminus(selfindex, i) = amp_forces(selfindex, i) end do end do do selfindex = 1, num_atoms do i = 1, 3 dforces_dparameters(selfindex)%twodarray(i, j) = & (fplus(selfindex, i) - fminus(selfindex, i)) / & (2.0d0 * d) end do end do parameters(j) = parameters(j) + d deallocate(fplus) deallocate(fminus) end do deallocate(amp_forces) call calculate_forces(image_no) end subroutine calculate_numerical_dforces_dparameters !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! used only in the image-centered mode. subroutine unravel_atomic_positions() do image_no = 1, num_images allocate(unraveled_atomic_positions(image_no)%onedarray(& 3 * num_atoms)) do index = 1, num_atoms do i = 1, 3 unraveled_atomic_positions(image_no)%onedarray(& 3 * (index - 1) + i) = atomic_positions(& image_no, 3 * (index - 1) + i) end do end do end do end subroutine unravel_atomic_positions !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine unravel_atomic_numbers() k = 0 do image_no = 1, num_images num_atoms = num_images_atoms(image_no) allocate(unraveled_atomic_numbers(& image_no)%onedarray(num_atoms)) do l = 1, num_atoms unraveled_atomic_numbers(image_no)%onedarray(l) & = atomic_numbers(k + l) end do k = k + num_atoms end do end subroutine unravel_atomic_numbers !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine unravel_neighborlists() k = 0 q = 0 do image_no = 1, num_images num_atoms = num_images_atoms(image_no) allocate(unraveled_neighborlists(image_no)%onedarray(& num_atoms)) do index = 1, num_atoms allocate(unraveled_neighborlists(image_no)%onedarray(& index)%onedarray(num_neighbors(k + index))) do p = 1, num_neighbors(k + index) unraveled_neighborlists(image_no)%onedarray(& index)%onedarray(p) = raveled_neighborlists(q + p)+1 end do q = q + num_neighbors(k + index) end do k = k + num_atoms end do end subroutine unravel_neighborlists !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine unravel_actual_forces() k = 0 do image_no = 1, num_images if (mode_signal == 1) then num_atoms = num_atoms else num_atoms = num_images_atoms(image_no) end if allocate(unraveled_actual_forces(image_no)%atom_forces(& num_atoms, 3)) do index = 1, num_atoms do i = 1, 3 unraveled_actual_forces(image_no)%atom_forces(& index, i) = actual_forces(k + index, i) end do end do k = k + num_atoms end do end subroutine unravel_actual_forces !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine unravel_fingerprints() k = 0 do image_no = 1, num_images num_atoms = & num_images_atoms(image_no) allocate(unraveled_fingerprints(& image_no)%onedarray(num_atoms)) do index = 1, num_atoms do element = 1, num_elements if (unraveled_atomic_numbers(& image_no)%onedarray(index)== & elements_numbers(element)) then allocate(unraveled_fingerprints(& image_no)%onedarray(index)%onedarray(& num_fingerprints_of_elements(element))) exit end if end do do l = 1, num_fingerprints_of_elements(element) unraveled_fingerprints(& image_no)%onedarray(index)%onedarray(l) = & raveled_fingerprints(k + index, l) end do end do k = k + num_atoms end do end subroutine unravel_fingerprints !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine unravel_fingerprintprimes() integer:: no_of_neighbors k = 0 m = 0 do image_no = 1, num_images num_atoms = & num_images_atoms(image_no) allocate(unraveled_fingerprintprimes(& image_no)%onedarray(num_atoms)) do selfindex = 1, num_atoms ! neighborindices list is generated. allocate(neighborindices(size(unraveled_neighborlists(& image_no)%onedarray(selfindex)%onedarray))) do p = 1, size(unraveled_neighborlists(& image_no)%onedarray(selfindex)%onedarray) neighborindices(p) = unraveled_neighborlists(& image_no)%onedarray(selfindex)%onedarray(p) end do no_of_neighbors = num_neighbors(k + selfindex) allocate(unraveled_fingerprintprimes(& image_no)%onedarray(selfindex)%onedarray(no_of_neighbors)) do nindex = 1, no_of_neighbors do nsymbol = 1, num_elements if (unraveled_atomic_numbers(& image_no)%onedarray(neighborindices(nindex)) == & elements_numbers(nsymbol)) then exit end if end do allocate(unraveled_fingerprintprimes(& image_no)%onedarray(selfindex)%onedarray(& nindex)%twodarray(3, num_fingerprints_of_elements(& nsymbol))) do p = 1, 3 do q = 1, num_fingerprints_of_elements(nsymbol) unraveled_fingerprintprimes(& image_no)%onedarray(selfindex)%onedarray(& nindex)%twodarray(p, q) = & raveled_fingerprintprimes(& 3 * m + 3 * nindex + p - 3, q) end do end do end do deallocate(neighborindices) m = m + no_of_neighbors end do k = k + num_atoms end do end subroutine unravel_fingerprintprimes !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! end subroutine calculate_loss !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! subroutine that deallocates variables subroutine deallocate_variables() use images_props use fingerprint_props use model_props use neuralnetwork !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! deallocating fingerprint_props if (allocated(num_fingerprints_of_elements) .EQV. .TRUE.) then deallocate(num_fingerprints_of_elements) end if if (allocated(raveled_fingerprints) .EQV. .TRUE.) then deallocate(raveled_fingerprints) end if if (allocated(raveled_fingerprintprimes) .EQV. .TRUE.) then deallocate(raveled_fingerprintprimes) end if ! deallocating images_props if (allocated(elements_numbers) .EQV. .TRUE.) then deallocate(elements_numbers) end if if (allocated(num_images_atoms) .EQV. .TRUE.) then deallocate(num_images_atoms) end if if (allocated(atomic_numbers) .EQV. .TRUE.) then deallocate(atomic_numbers) end if if (allocated(num_neighbors) .EQV. .TRUE.) then deallocate(num_neighbors) end if if (allocated(raveled_neighborlists) .EQV. .TRUE.) then deallocate(raveled_neighborlists) end if if (allocated(actual_energies) .EQV. .TRUE.) then deallocate(actual_energies) end if if (allocated(actual_forces) .EQV. .TRUE.) then deallocate(actual_forces) end if if (allocated(atomic_positions) .EQV. .TRUE.) then deallocate(atomic_positions) end if ! deallocating neuralnetwork if (allocated(min_fingerprints) .EQV. .TRUE.) then deallocate(min_fingerprints) end if if (allocated(max_fingerprints) .EQV. .TRUE.) then deallocate(max_fingerprints) end if if (allocated(no_layers_of_elements) .EQV. .TRUE.) then deallocate(no_layers_of_elements) end if if (allocated(no_nodes_of_elements) .EQV. .TRUE.) then deallocate(no_nodes_of_elements) end if !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! end subroutine deallocate_variables !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! amp-0.6/amp/utilities.py0000644000175000017500000012232513137634440015170 0ustar muammarmuammar#!/usr/bin/env python import numpy as np import hashlib import time import os import sys import copy import math import random import signal import tarfile import traceback from datetime import datetime from getpass import getuser from ase import io as aseio from ase.db import connect from ase.calculators.calculator import PropertyNotImplementedError try: import cPickle as pickle # Python2 except ImportError: import pickle # Python3 # Parallel processing ######################################################## def assign_cores(cores, log=None): """Tries to guess cores from environment. If fed a log object, will write its progress. """ log = Logger(None) if log is None else log def fail(q, traceback_text=None): msg = ('Auto core detection is either not set up or not working for' ' your version of %s. You are invited to submit a patch to ' 'return a dictionary of the form {nodename: ncores} for this' ' batching system. The environment contents were dumped to ' 'the log file, as well as any traceback that caused the ' 'error.') log(msg % q) log('Environment dump:') for key, value in os.environ.items(): log('%s: %s' % (key, value)) if traceback_text: log('\n' + '='*70 + '\nTraceback of last error encountered:') log(traceback_text) raise NotImplementedError(msg % q) def success(q, cores, log): log('Parallel configuration determined from environment for %s:' % q) for key, value in cores.items(): log(' %s: %i' % (key, value)) if cores is not None: q = '' if cores == 1: log('Serial operation on one core specified.') return cores else: try: cores = int(cores) except TypeError: cores = cores success(q, cores, log) return cores else: cores = {'localhost': cores} success(q, cores, log) return cores if 'SLURM_NODELIST' in os.environ.keys(): q = 'SLURM' try: nnodes = int(os.environ['SLURM_NNODES']) taskspernode = int(os.environ['SLURM_NTASKS_PER_NODE']) if nnodes == 1: cores = {'localhost': taskspernode} else: nodes = os.environ['SLURM_NODELIST'] if '[' in nodes: # Formatted funny like 'node[572,578]'. prename, numbers = nodes.split('[') numbers = numbers[:-1].split(',') nodes = [prename + _ for _ in numbers] else: nodes = nodes.split(',') cores = {node: taskspernode for node in nodes} except: # Get the traceback to log it. fail(q, traceback_text=traceback.format_exc()) elif 'PBS_NODEFILE' in os.environ.keys(): fail(q='PBS') elif 'LOADL_PROCESSOR_LIST' in os.environ.keys(): fail(q='LOADL') elif 'PE_HOSTFILE' in os.environ.keys(): q = 'SGE' try: hostfile = os.getenv('PE_HOSTFILE') cores = {} with open(hostfile) as f: for i, istr in enumerate(f): hostname, nc = istr.split()[0:2] nc = int(nc) cores[hostname] = nc except: # Get the traceback to log it. fail(q, traceback_text=traceback.format_exc()) else: import multiprocessing ncores = multiprocessing.cpu_count() cores = {'localhost': ncores} log('No queuing system detected; single machine assumed.') q = '' success(q, cores, log) return cores class MessageDictionary: """Standard container for all messages (typically requests, via zmq.context.socket.send_pyobj) sent from the workers to the master. This returns a simple dictionary. This is roughly email format. Initialize with process id (e.g., 'from'). Call with subject and data (body). """ def __init__(self, process_id): self._process_id = process_id def __call__(self, subject, data=None): d = {'id': self._process_id, 'subject': subject, 'data': data} return d def make_sublists(masterlist, n): """Randomly divides the masterlist into n sublists of roughly equal size. The intended use is to divide a keylist and assign keys to each task in parallel processing. This also destroys the masterlist (to save some memory). """ masterlist = list(masterlist) np.random.shuffle(masterlist) N = len(masterlist) sublist_lengths = [ N // n if _ >= (N % n) else N // n + 1 for _ in range(n)] sublists = [] for sublist_length in sublist_lengths: sublists.append([masterlist.pop() for _ in range(sublist_length)]) return sublists def setup_parallel(parallel, workercommand, log): """Starts the worker processes and the master to control them. This makes an SSH connection to each node (including the one the master process runs on), then creates the specified number of processes on each node through its SSH connection. Then sets up ZMQ for efficienty communication between the worker processes and the master process. Uses the parallel dictionary as defined in amp.Amp. log is an Amp logger. module is the name of the module to be called, which is usually given by self.calc.__module, etc. workercommand is stub of the command used to start the servers, typically like "python -m amp.descriptor.gaussian". Appended to this will be " &" where is the unique ID assigned to each process and is the address of the server, like 'node321:34292'. Returns ------- server : (a ZMQ socket) The ssh connections (pxssh instances; if these objects are destroyed pxssh will close the sessions) the pid_count, which is the total number of workers started. Each worker can be communicated directly through its PID, an integer between 0 and pid_count """ import zmq from socket import gethostname log(' Parallel processing.') serverhostname = gethostname() # Establish server session. context = zmq.Context() server = context.socket(zmq.REP) port = server.bind_to_random_port('tcp://*') serversocket = '%s:%s' % (serverhostname, port) log(' Established server at %s.' % serversocket) workercommand += ' %s ' + serversocket log(' Establishing worker sessions.') connections = [] pid_count = 0 for workerhostname, nprocesses in parallel['cores'].items(): pids = range(pid_count, pid_count + nprocesses) pid_count += nprocesses connections.append(start_workers(pids, workerhostname, workercommand, log, parallel['envcommand'])) return server, connections, pid_count def start_workers(process_ids, workerhostname, workercommand, log, envcommand): """A function to start a new SSH session and establish processes on that session. """ if workerhostname != 'localhost': workercommand += ' &' log(' Starting non-local connections.') pxssh = importer('pxssh') ssh = pxssh.pxssh() ssh.login(workerhostname, getuser()) if envcommand is not None: log('Environment command: %s' % envcommand) ssh.sendline(envcommand) ssh.readline() for process_id in process_ids: ssh.sendline(workercommand % process_id) ssh.expect('') ssh.expect('') log(' Session %i (%s): %s' % (process_id, workerhostname, ssh.before.strip())) return ssh import pexpect log(' Starting local connections.') children = [] for process_id in process_ids: child = pexpect.spawn(workercommand % process_id) child.expect('') child.expect('') log(' Session %i (%s): %s' % (process_id, workerhostname, child.before.strip())) children.append(child) return children # Data and logging ########################################################### class FileDatabase: """Using a database file, such as shelve or sqlitedict, that can handle multiple processes writing to the file is hard. Therefore, we take the stupid approach of having each database entry be a separate file. This class behaves essentially like shelve, but saves each dictionary entry as a plain pickle file within the directory, with the filename corresponding to the dictionary key (which must be a string). Like shelve, this also keeps an internal (memory dictionary) representation of the variables that have been accessed. Also includes an archive feature, where files are instead added to a file called 'archive.tar.gz' to save disk space. If an entry exists in both the loose and archive formats, the loose is taken to be the new (correct) value. """ def __init__(self, filename): """Open the filename at specified location. flag is ignored; this format is always capable of both reading and writing.""" if not filename.endswith(os.extsep + 'ampdb'): filename += os.extsep + 'ampdb' self.path = filename self.loosepath = os.path.join(self.path, 'loose') self.tarpath = os.path.join(self.path, 'archive.tar.gz') if not os.path.exists(self.path): os.mkdir(self.path) os.mkdir(self.loosepath) self._memdict = {} # Items already accessed; stored in memory. @classmethod def open(Cls, filename, flag=None): """Open present for compatibility with shelve. flag is ignored; this format is always capable of both reading and writing. """ return Cls(filename=filename) def close(self): """Only present for compatibility with shelve. """ return def keys(self): """Return list of keys, both of in-memory and out-of-memory items. """ keys = os.listdir(self.loosepath) if os.path.exists(self.tarpath): with tarfile.open(self.tarpath) as tf: keys = list(set(keys + tf.getnames())) return keys def values(self): """Return list of values, both of in-memory and out-of-memory items. This moves all out-of-memory items into memory. """ keys = self.keys() return [self[key] for key in keys] def __len__(self): return len(self.keys()) def __setitem__(self, key, value): self._memdict[key] = value path = os.path.join(self.loosepath, str(key)) if os.path.exists(path): with open(path, 'r') as f: if f.read() == pickle.dumps(value): return # Nothing to update. with open(path, 'wb') as f: pickle.dump(value, f) def __getitem__(self, key): if key in self._memdict: return self._memdict[key] keypath = os.path.join(self.loosepath, key) if os.path.exists(keypath): with open(keypath, 'rb') as f: return pickle.load(f) elif os.path.exists(self.tarpath): with tarfile.open(self.tarpath) as tf: return pickle.load(tf.extractfile(key)) else: raise KeyError(str(key)) def update(self, newitems): for key, value in newitems.items(): self.__setitem__(key, value) def archive(self): """Cleans up to save disk space and reduce huge number of files. That is, puts all files into an archive. Compresses all files in /loose and places them in /archive.tar.gz. If archive exists, appends/modifies. """ loosefiles = os.listdir(self.loosepath) print('Contains %i loose entries.' % len(loosefiles)) if len(loosefiles) == 0: print(' -> No action taken.') return if os.path.exists(self.tarpath): with tarfile.open(self.tarpath) as tf: names = [_ for _ in tf.getnames() if _ not in os.listdir(self.loosepath)] for name in names: tf.extract(member=name, path=self.loosepath) loosefiles = os.listdir(self.loosepath) print('Compressing %i entries.' % len(loosefiles)) with tarfile.open(self.tarpath, 'w:gz') as tf: for file in loosefiles: tf.add(name=os.path.join(self.loosepath, file), arcname=file) print('Cleaning up: removing %i files.' % len(loosefiles)) for file in loosefiles: os.remove(os.path.join(self.loosepath, file)) class Data: """Serves as a container (dictionary-like) for (key, value) pairs that also serves to calculate them. Works by default with python's shelve module, but something that is built to share the same commands as shelve will work fine; just specify this in dbinstance. Designed to hold things like neighborlists, which have a hash, value format. This will work like a dictionary in that items can be accessed with data[key], but other advanced dictionary functions should be accessed with through the .d attribute: >>> data = Data(...) >>> data.open() >>> keys = data.d.keys() >>> values = data.d.values() """ def __init__(self, filename, db=FileDatabase, calculator=None): self.calc = calculator self.db = db self.filename = filename self.d = None def calculate_items(self, images, parallel, log=None): """Calculates the data value with 'calculator' for the specified images. images is a dictionary, and the same keys will be used for the current database. """ if log is None: log = Logger(None) if self.d is not None: self.d.close() self.d = None log(' Data stored in file %s.' % self.filename) d = self.db.open(self.filename, 'r') calcs_needed = list(set(images.keys()).difference(d.keys())) dblength = len(d) d.close() log(' File exists with %i total images, %i of which are needed.' % (dblength, len(images) - len(calcs_needed))) log(' %i new calculations needed.' % len(calcs_needed)) if len(calcs_needed) == 0: return if parallel['cores'] == 1: d = self.db.open(self.filename, 'c') for key in calcs_needed: d[key] = self.calc.calculate(images[key], key) d.close() # Necessary to get out of write mode and unlock? log(' Calculated %i new images.' % len(calcs_needed)) else: python = sys.executable workercommand = '%s -m %s' % (python, self.calc.__module__) server, connections, n_pids = setup_parallel(parallel, workercommand, log) globals = self.calc.globals keyed = self.calc.keyed keys = make_sublists(calcs_needed, n_pids) results = {} # All incoming requests will be dictionaries with three keys. # d['id']: process id number, assigned when process created above. # d['subject']: what the message is asking for / telling you # d['data']: optional data passed from the worker. active = 0 # count of processes actively calculating log(' Parallel calculations starting...', tic='parallel') active = n_pids # currently active workers while True: message = server.recv_pyobj() if message['subject'] == '': server.send_pyobj(self.calc.parallel_command) elif message['subject'] == '': request = message['data'] # Variable name. if request == 'images': server.send_pyobj({k: images[k] for k in keys[int(message['id'])]}) elif request in keyed: server.send_pyobj({k: keyed[request][k] for k in keys[int(message['id'])]}) else: server.send_pyobj(globals[request]) elif message['subject'] == '': result = message['data'] server.send_string('meaningless reply') active -= 1 log(' Process %s returned %i results.' % (message['id'], len(result))) results.update(result) elif message['subject'] == '': server.send_string('meaningless reply') if active == 0: break log(' %i new results.' % len(results)) log(' ...parallel calculations finished.', toc='parallel') log(' Adding new results to database.') d = self.db.open(self.filename, 'c') d.update(results) d.close() # Necessary to get out of write mode and unlock? self.d = None def __getitem__(self, key): self.open() return self.d[key] def close(self): """Safely close the database. """ if self.d: self.d.close() self.d = None def open(self, mode='r'): """Open the database connection with mode specified. """ if self.d is None: self.d = self.db.open(self.filename, mode) def __del__(self): self.close() class Logger: """Logger that can also deliver timing information. Parameters ---------- file : str File object or path to the file to write to. Or set to None for a logger that does nothing. """ def __init__(self, file): if file is None: self.file = None return if isinstance(file, str): self.filename = file file = open(file, 'a') self.file = file self.tics = {} def tic(self, label=None): """Start a timer. Parameters ---------- label : str Label for managing multiple timers. """ if self.file is None: return if label: self.tics[label] = time.time() else: self._tic = time.time() def __call__(self, message, toc=None, tic=False): """Writes message to the log file. Parameters --------- message : str Message to be written. toc : bool or str If toc=True or toc=label, it will append timing information in minutes to the timer. tic : bool or str If tic=True or tic=label, will start the generic timer or a timer associated with label. Equivalent to self.tic(label). """ if self.file is None: return dt = '' if toc: if toc is True: tic = self._tic else: tic = self.tics[toc] dt = (time.time() - tic) / 60. dt = ' %.1f min.' % dt if self.file.closed: self.file = open(self.filename, 'a') self.file.write(message + dt + '\n') self.file.flush() if tic: if tic is True: self.tic() else: self.tic(label=tic) def make_filename(label, base_filename): """Creates a filename from the label and the base_filename which should be a string. Returns None if label is None; that is, it only saves output if a label is specified. Parameters ---------- label : str Prefix. base_filename : str Basic name of the file. """ if label is None: return None if not label: filename = base_filename else: filename = os.path.join(label + base_filename) return filename # Images and hashing ######################################################### def get_hash(atoms): """Creates a unique signature for a particular ASE atoms object. This is used to check whether an image has been seen before. This is just an md5 hash of a string representation of the atoms object. Parameters ---------- atoms : ASE dict ASE atoms object. Returns ------- Hash string key of 'atoms'. """ string = str(atoms.pbc) for number in atoms.cell.flatten(): string += '%.15f' % number string += str(atoms.get_atomic_numbers()) for number in atoms.get_positions().flatten(): string += '%.15f' % number md5 = hashlib.md5(string.encode('utf-8')) hash = md5.hexdigest() return hash def hash_images(images, log=None, ordered=False): """ Converts input images -- which may be a list, a trajectory file, or a database -- into a dictionary indexed by their hashes. Returns this dictionary. If ordered is True, returns an OrderedDict. When duplicate images are encountered (based on encountering an identical hash), a warning is written to the logfile. The number of duplicates of each image can be accessed by examinging dict_images.metadata['duplicates'], where dict_images is the returned dictionary. """ if log is None: log = Logger(None) if images is None: return elif hasattr(images, 'keys'): log(' %i unique images after hashing.' % len(images)) return images # Apparently already hashed. else: # Need to be hashed, and possibly read from file. if isinstance(images, str): log('Attempting to read images from file %s.' % images) extension = os.path.splitext(images)[1] from ase import io if extension == '.traj': images = io.Trajectory(images, 'r') elif extension == '.db': images = [row.toatoms() for row in connect(images, 'db').select(None)] # images converted to dictionary form; key is hash of image. log('Hashing images...', tic='hash') dict_images = MetaDict() dict_images.metadata['duplicates'] = {} dup = dict_images.metadata['duplicates'] if ordered is True: from collections import OrderedDict dict_images = OrderedDict() for image in images: hash = get_hash(image) if hash in dict_images.keys(): log('Warning: Duplicate image (based on identical hash).' ' Was this expected? Hash: %s' % hash) if hash in dup.keys(): dup[hash] += 1 else: dup[hash] = 2 dict_images[hash] = image log(' %i unique images after hashing.' % len(dict_images)) log('...hashing completed.', toc='hash') return dict_images def check_images(images, forces): """Checks that all images have energies, and optionally forces, calculated, so that they can be used for training. Raises a MissingDataError if any are missing.""" missing_energies, missing_forces = 0, 0 for index, image in enumerate(images.values()): try: image.get_potential_energy() except PropertyNotImplementedError: missing_energies += 1 if forces is True: try: image.get_forces() except PropertyNotImplementedError: missing_forces += 1 if missing_energies + missing_forces == 0: return msg = '' if missing_energies > 0: msg += 'Missing energy in {} image(s).'.format(missing_energies) if missing_forces > 0: msg += ' Missing forces in {} image(s).'.format(missing_forces) raise MissingDataError(msg) def randomize_images(images, fraction=0.8): """Randomly assigns 'fraction' of the images to a training set and (1 - 'fraction') to a test set. Returns two lists of ASE images. Parameters ---------- images : list or str List of ASE atoms objects in ASE format. This can also be the path to an ASE trajectory (.traj) or database (.db) file. fraction : float Portion of train_images to all images. Returns ------- train_images, test_images : list Lists of train and test images. """ file_opened = False if type(images) == str: extension = os.path.splitext(images)[1] if extension == '.traj': images = aseio.Trajectory(images, 'r') elif extension == '.db': images = aseio.read(images) file_opened = True trainingsize = int(fraction * len(images)) testsize = len(images) - trainingsize testindices = [] while len(testindices) < testsize: next = np.random.randint(len(images)) if next not in testindices: testindices.append(next) testindices.sort() trainindices = [index for index in range(len(images)) if index not in testindices] train_images = [images[index] for index in trainindices] test_images = [images[index] for index in testindices] if file_opened: images.close() return train_images, test_images # Custom exceptions ########################################################## class ConvergenceOccurred(Exception): """ Kludge to decide when scipy's optimizers are complete. """ pass class TrainingConvergenceError(Exception): """Error to be raised if training does not converge. """ pass class MissingDataError(Exception): """Error to be raised if any images are missing key data, like energy or forces.""" pass # Miscellaneous ############################################################## def string2dict(text): """Converts a string into a dictionary. Basically just calls `eval` on it, but supplies words like OrderedDict and matrix. """ try: dictionary = eval(text) except NameError: from collections import OrderedDict from numpy import array, matrix dictionary = eval(text) return dictionary def now(with_utc=False): """ Returns ------- String of current time. """ local = datetime.now().isoformat().split('.')[0] utc = datetime.utcnow().isoformat().split('.')[0] if with_utc: return '%s (%s UTC)' % (local, utc) else: return local logo = """ oo o o oooooo o o oo oo o o o o o o o o o o o o o o o o o o oooooooo o o o oooooo o o o o o o o o o o o o o o o """ def importer(name): """Handles strange import cases, like pxssh which might show up in eithr the package pexpect or pxssh. """ if name == 'pxssh': try: import pxssh except ImportError: try: from pexpect import pxssh except ImportError: raise ImportError('pxssh not found!') return pxssh elif name == 'NeighborList': try: from ase.neighborlist import NeighborList except ImportError: # We're on ASE 3.10 or older from ase.calculators.neighborlist import NeighborList return NeighborList # Amp Simulated Annealer ###################################################### class Annealer(object): """ Inspired by the simulated annealing implementation of Richard J. Wagner and Matthew T. Perry at https://github.com/perrygeo/simanneal. Performs simulated annealing by calling functions to calculate loss and make moves on a state. The temperature schedule for annealing may be provided manually or estimated automatically. Can be used by something like: >>> from amp import Amp >>> from amp.descriptor.gaussian import Gaussian >>> from amp.model.neuralnetwork import NeuralNetwork >>> calc = Amp(descriptor=Gaussian(), model=NeuralNetwork()) which will initialize tha calc object as usual, and then >>> from amp.utilities import Annealer >>> Annealer(calc=calc, images=images) which will perform simulated annealing global search in parameters space, and finally >>> calc.train(images=images) for gradient descent optimization. """ Tmax = 20.0 # Max (starting) temperature Tmin = 2.5 # Min (ending) temperature steps = 10000 # Number of iterations updates = steps / 200 # Number of updates (an update prints to log) copy_strategy = 'copy' user_exit = False save_state_on_exit = False def __init__(self, calc, images, Tmax=None, Tmin=None, steps=None, updates=None): if Tmax is not None: self.Tmax = Tmax if Tmin is not None: self.Tmin = Tmin if steps is not None: self.steps = steps if updates is not None: self.updates = updates self.calc = calc self.calc._log('\nAmp simulated annealer started. ' + now() + '\n') self.calc._log('Descriptor: %s' % self.calc.descriptor.__class__.__name__) self.calc._log('Model: %s' % self.calc.model.__class__.__name__) images = hash_images(images, log=self.calc._log) self.calc._log('\nDescriptor\n==========') # Derivatives of fingerprints need to be calculated if train_forces is # True. calculate_derivatives = True self.calc.descriptor.calculate_fingerprints( images=images, parallel=self.calc._parallel, log=self.calc._log, calculate_derivatives=calculate_derivatives) # Setting up calc.model.vector() self.calc.model.fit(trainingimages=images, descriptor=self.calc.descriptor, log=self.calc._log, parallel=self.calc._parallel, only_setup=True,) # Truning off ConvergenceOccured exception and log_losses initial_raise_ConvergenceOccurred = \ self.calc.model.lossfunction.raise_ConvergenceOccurred initial_log_losses = self.calc.model.lossfunction.log_losses self.calc.model.lossfunction.log_losses = False self.calc.model.lossfunction.raise_ConvergenceOccurred = False initial_state = self.calc.model.vector.copy() self.state = self.copy_state(initial_state) signal.signal(signal.SIGINT, self.set_user_exit) self.calc._log('\nAnnealing\n=========\n') bestState, bestLoss = self.anneal() # Taking the best state self.calc.model.vector = np.array(bestState) # Returning back the changed arguments self.calc.model.lossfunction.log_losses = initial_log_losses self.calc.model.lossfunction.raise_ConvergenceOccurred = \ initial_raise_ConvergenceOccurred # cleaning up sessions self.calc.model.lossfunction._step = 0 self.calc.model.lossfunction._cleanup() calc = self.calc @staticmethod def round_figures(x, n): """Returns x rounded to n significant figures.""" return round(x, int(n - math.ceil(math.log10(abs(x))))) @staticmethod def time_string(seconds): """Returns time in seconds as a string formatted HHHH:MM:SS.""" s = int(round(seconds)) # round to nearest second h, s = divmod(s, 3600) # get hours and remainder m, s = divmod(s, 60) # split remainder into minutes and seconds return '%4i:%02i:%02i' % (h, m, s) def save_state(self, fname=None): """Saves state """ if not fname: date = datetime.datetime.now().isoformat().split(".")[0] fname = date + "_loss_" + str(self.get_loss()) + ".state" print("Saving state to: %s" % fname) with open(fname, "w") as fh: pickle.dump(self.state, fh) def move(self, state): """Create a state change """ move_step = np.random.rand(len(state)) * 2. - 1. move_step *= 0.0005 for _ in range(len(state)): state[_] = state[_] * (1 + move_step[_]) return state def get_loss(self, state): """Calculate state's loss """ lossfxn = \ self.calc.model.lossfunction.get_loss(np.array(state), lossprime=False,)['loss'] return lossfxn def set_user_exit(self, signum, frame): """Raises the user_exit flag, further iterations are stopped """ self.user_exit = True def set_schedule(self, schedule): """Takes the output from `auto` and sets the attributes """ self.Tmax = schedule['tmax'] self.Tmin = schedule['tmin'] self.steps = int(schedule['steps']) def copy_state(self, state): """Returns an exact copy of the provided state Implemented according to self.copy_strategy, one of * deepcopy : use copy.deepcopy (slow but reliable) * slice: use list slices (faster but only works if state is list-like) * method: use the state's copy() method """ if self.copy_strategy == 'deepcopy': return copy.deepcopy(state) elif self.copy_strategy == 'slice': return state[:] elif self.copy_strategy == 'copy': return state.copy() def update(self, step, T, L, acceptance, improvement): """Prints the current temperature, loss, acceptance rate, improvement rate, elapsed time, and remaining time. The acceptance rate indicates the percentage of moves since the last update that were accepted by the Metropolis algorithm. It includes moves that decreased the loss, moves that left the loss unchanged, and moves that increased the loss yet were reached by thermal excitation. The improvement rate indicates the percentage of moves since the last update that strictly decreased the loss. At high temperatures it will include both moves that improved the overall state and moves that simply undid previously accepted moves that increased the loss by thermal excititation. At low temperatures it will tend toward zero as the moves that can decrease the loss are exhausted and moves that would increase the loss are no longer thermally accessible. """ elapsed = time.time() - self.start if step == 0: self.calc._log('\n') header = ' %5s %12s %12s %7s %7s %10s %10s' self.calc._log(header % ('Step', 'Temperature', 'Loss (SSD)', 'Accept', 'Improve', 'Elapsed', 'Remaining')) self.calc._log(header % ('=' * 5, '=' * 12, '=' * 12, '=' * 7, '=' * 7, '=' * 10, '=' * 10,)) self.calc._log( ' %5i %12.2e %12.4e %s ' % (step, T, L, self.time_string(elapsed))) else: remain = (self.steps - step) * (elapsed / step) self.calc._log(' %5i %12.2e %12.4e %7.2f%% %7.2f%% %s %s' % (step, T, L, 100.0 * acceptance, 100.0 * improvement, self.time_string(elapsed), self.time_string(remain))) def anneal(self): """Minimizes the loss of a system by simulated annealing. Parameters --------- state An initial arrangement of the system Returns ------- state, loss The best state and loss found. """ step = 0 self.start = time.time() # Precompute factor for exponential cooling from Tmax to Tmin if self.Tmin <= 0.0: raise Exception('Exponential cooling requires a minimum "\ "temperature greater than zero.') Tfactor = -math.log(self.Tmax / self.Tmin) # Note initial state T = self.Tmax L = self.get_loss(self.state) prevState = self.copy_state(self.state) prevLoss = L bestState = self.copy_state(self.state) bestLoss = L trials, accepts, improves = 0, 0, 0 if self.updates > 0: updateWavelength = self.steps / self.updates self.update(step, T, L, None, None) # Attempt moves to new states while step < (self.steps - 1) and not self.user_exit: step += 1 T = self.Tmax * math.exp(Tfactor * step / self.steps) self.state = self.move(self.state) L = self.get_loss(self.state) dL = L - prevLoss trials += 1 if dL > 0.0 and math.exp(-dL / T) < random.random(): # Restore previous state self.state = self.copy_state(prevState) L = prevLoss else: # Accept new state and compare to best state accepts += 1 if dL < 0.0: improves += 1 prevState = self.copy_state(self.state) prevLoss = L if L < bestLoss: bestState = self.copy_state(self.state) bestLoss = L if self.updates > 1: if step // updateWavelength > (step - 1) // updateWavelength: self.update( step, T, L, accepts / trials, improves / trials) trials, accepts, improves = 0, 0, 0 # line break after progress output print('') self.state = self.copy_state(bestState) if self.save_state_on_exit: self.save_state() # Return best state and loss return bestState, bestLoss def auto(self, minutes, steps=2000): """Minimizes the loss of a system by simulated annealing with automatic selection of the temperature schedule. Keyword arguments: state -- an initial arrangement of the system minutes -- time to spend annealing (after exploring temperatures) steps -- number of steps to spend on each stage of exploration Returns the best state and loss found. """ def run(T, steps): """Anneals a system at constant temperature and returns the state, loss, rate of acceptance, and rate of improvement. """ L = self.get_loss() prevState = self.copy_state(self.state) prevLoss = L accepts, improves = 0, 0 for step in range(steps): self.move() L = self.get_loss() dL = L - prevLoss if dL > 0.0 and math.exp(-dL / T) < random.random(): self.state = self.copy_state(prevState) L = prevLoss else: accepts += 1 if dL < 0.0: improves += 1 prevState = self.copy_state(self.state) prevLoss = L return L, float(accepts) / steps, float(improves) / steps step = 0 self.start = time.time() # Attempting automatic simulated anneal... # Find an initial guess for temperature T = 0.0 L = self.get_loss() self.update(step, T, L, None, None) while T == 0.0: step += 1 self.move() T = abs(self.get_loss() - L) # Search for Tmax - a temperature that gives 98% acceptance L, acceptance, improvement = run(T, steps) step += steps while acceptance > 0.98: T = self.round_figures(T / 1.5, 2) L, acceptance, improvement = run(T, steps) step += steps self.update(step, T, L, acceptance, improvement) while acceptance < 0.98: T = self.round_figures(T * 1.5, 2) L, acceptance, improvement = run(T, steps) step += steps self.update(step, T, L, acceptance, improvement) Tmax = T # Search for Tmin - a temperature that gives 0% improvement while improvement > 0.0: T = self.round_figures(T / 1.5, 2) L, acceptance, improvement = run(T, steps) step += steps self.update(step, T, L, acceptance, improvement) Tmin = T # Calculate anneal duration elapsed = time.time() - self.start duration = self.round_figures(int(60.0 * minutes * step / elapsed), 2) print('') # New line after auto() output # Don't perform anneal, just return params return {'tmax': Tmax, 'tmin': Tmin, 'steps': duration} class MetaDict(dict): """Dictionary that can also store metadata. Useful for images dictionary so that images can still be iterated by keys. """ metadata = {} amp-0.6/amp/descriptor/0002755000175000017500000000000013142666751014764 5ustar muammarmuammaramp-0.6/amp/descriptor/__init__.py0000644000175000017500000000013513137634440017064 0ustar muammarmuammar#!/usr/bin/env python """ Folder that contains different local environment descriptors. """ amp-0.6/amp/descriptor/zernike.py0000644000175000017500000011604513137634440017004 0ustar muammarmuammarimport numpy as np from numpy import sqrt from ase.data import atomic_numbers from ase.calculators.calculator import Parameters from scipy.special import sph_harm from ..utilities import Data, Logger, importer from .cutoffs import Cosine, Polynomial, dict2cutoff NeighborList = importer('NeighborList') try: from .. import fmodules except ImportError: fmodules = None class Zernike(object): """Class that calculates Zernike fingerprints. Parameters ---------- cutoff : object or float Cutoff function, typically from amp.descriptor.cutoffs. Can be also fed as a float representing the radius above which neighbor interactions are ignored; in this case a cosine cutoff function will be employed. Default is a 6.5-Angstrom cosine cutoff. Gs : dict Dictionary of symbols and dictionaries for making symmetry functions. Either auto-genetrated, or given in the following form, for example: >>> Gs = {"Au": {"Au": 3., "O": 2.}, "O": {"Au": 5., "O": 10.}} This is basically the same as \eta in Eq. (16) of https://doi.org/10.1016/j.cpc.2016.05.010. nmax : integer or dict Maximum degree of Zernike polynomials that will be included in the fingerprint vector. Can be different values for different species fed as a dictionary with chemical elements as keys. dblabel : str Optional separate prefix/location for database files, including fingerprints, fingerprint derivatives, and neighborlists. This file location can be shared between calculator instances to avoid re-calculating redundant information. If not supplied, just uses the value from label. elements : list List of allowed elements present in the system. If not provided, will be found automatically. version : str Version of fingerprints. mode : str Can be either 'atom-centered' or 'image-centered'. fortran : bool If True, will use fortran modules, if False, will not. Raises ------ RuntimeError, TypeError """ def __init__(self, cutoff=Cosine(6.5), Gs=None, nmax=5, dblabel=None, elements=None, version='2016.02', mode='atom-centered', fortran=True): # Check of the version of descriptor, particularly if restarting. compatibleversions = ['2016.02', ] if (version is not None) and version not in compatibleversions: raise RuntimeError('Error: Trying to use Zernike fingerprints' ' version %s, but this module only supports' ' versions %s. You may need an older or ' ' newer version of Amp.' % (version, compatibleversions)) else: version = compatibleversions[-1] # Check that the mode is atom-centered. if mode != 'atom-centered': raise RuntimeError('Zernike scheme only works ' 'in atom-centered mode. %s ' 'specified.' % mode) # If the cutoff is provided as a number, Cosine function will be used # by default. if isinstance(cutoff, int) or isinstance(cutoff, float): cutoff = Cosine(cutoff) # If the cutoff is provided as a dictionary, assume we need to load it # with dict2cutoff. if type(cutoff) is dict: cutoff = dict2cutoff(cutoff) # The parameters dictionary contains the minimum information # to produce a compatible descriptor; that is, one that gives # an identical fingerprint when fed an ASE image. p = self.parameters = Parameters( {'importname': '.descriptor.zernike.Zernike', 'mode': 'atom-centered'}) p.version = version p.cutoff = cutoff.todict() if p.cutoff['name'] == 'Polynomial': self.gamma = cutoff.gamma p.Gs = Gs p.nmax = nmax p.elements = elements self.dblabel = dblabel self.fortran = fortran self.parent = None # Can hold a reference to main Amp instance. def tostring(self): """Returns an evaluatable representation of the calculator that can be used to restart the calculator.""" return self.parameters.tostring() def calculate_fingerprints(self, images, parallel=None, log=None, calculate_derivatives=False): """Calculates the fingerpints of the images, for the ones not already done. Parameters ---------- images : list or str List of ASE atoms objects with positions, symbols, energies, and forces in ASE format. This is the training set of data. This can also be the path to an ASE trajectory (.traj) or database (.db) file. Energies can be obtained from any reference, e.g. DFT calculations. parallel : dict Configuration for parallelization. Should be in same form as in amp.Amp. log : Logger object Write function at which to log data. Note this must be a callable function. calculate_derivatives : bool Decides whether or not fingerprintprimes should also be calculated. """ if parallel is None: parallel = {'cores': 1} log = Logger(file=None) if log is None else log if (self.dblabel is None) and hasattr(self.parent, 'dblabel'): self.dblabel = self.parent.dblabel self.dblabel = 'amp-data' if self.dblabel is None else self.dblabel p = self.parameters if p.cutoff['name'] == 'Cosine': log('Cutoff radius: %.2f ' % p.cutoff['kwargs']['Rc']) else: log('Cutoff radius: %.2f and gamma=%i ' % (p.cutoff['kwargs']['Rc'], self.gamma)) log('Cutoff function: %s' % repr(dict2cutoff(p.cutoff))) if p.elements is None: log('Finding unique set of elements in training data.') p.elements = set([atom.symbol for atoms in images.values() for atom in atoms]) p.elements = sorted(p.elements) log('%i unique elements included: ' % len(p.elements) + ', '.join(p.elements)) log('Maximum degree of Zernike polynomials:') if isinstance(p.nmax, dict): for _ in p.nmax.keys(): log(' %2s: %d' % (_, p.nmax[_])) else: log('nmax: %d' % p.nmax) if p.Gs is None: log('No coefficient for atomic density function supplied; ' 'creating defaults.') p.Gs = generate_coefficients(p.elements) log('Coefficients of atomic density function for each element:') for _ in p.Gs.keys(): log(' %2s: %s' % (_, str(p.Gs[_]))) # Counts the number of descriptors for each element. no_of_descriptors = {} for element in p.elements: count = 0 if isinstance(p.nmax, dict): for n in range(p.nmax[element] + 1): for l in range(n + 1): if (n - l) % 2 == 0: count += 1 else: for n in range(p.nmax + 1): for l in range(n + 1): if (n - l) % 2 == 0: count += 1 no_of_descriptors[element] = count log('Number of descriptors for each element:') for element in p.elements: log(' %2s: %d' % (element, no_of_descriptors.pop(element))) log('Calculating neighborlists...', tic='nl') if not hasattr(self, 'neighborlist'): calc = NeighborlistCalculator(cutoff=p.cutoff['kwargs']['Rc']) self.neighborlist = Data(filename='%s-neighborlists' % self.dblabel, calculator=calc) self.neighborlist.calculate_items(images, parallel=parallel, log=log) log('...neighborlists calculated.', toc='nl') log('Fingerprinting images...', tic='fp') if not hasattr(self, 'fingerprints'): calc = FingerprintCalculator(neighborlist=self.neighborlist, Gs=p.Gs, nmax=p.nmax, cutoff=p.cutoff, fortran=self.fortran) self.fingerprints = Data(filename='%s-fingerprints' % self.dblabel, calculator=calc) self.fingerprints.calculate_items(images, parallel=parallel, log=log) log('...fingerprints calculated.', toc='fp') if calculate_derivatives: log('Calculating fingerprint derivatives of images...', tic='derfp') if not hasattr(self, 'fingerprintprimes'): calc = \ FingerprintPrimeCalculator(neighborlist=self.neighborlist, Gs=p.Gs, nmax=p.nmax, cutoff=p.cutoff, fortran=self.fortran) self.fingerprintprimes = \ Data(filename='%s-fingerprint-primes' % self.dblabel, calculator=calc) self.fingerprintprimes.calculate_items( images, parallel=parallel, log=log) log('...fingerprint derivatives calculated.', toc='derfp') # Calculators ################################################################# # Neighborlist Calculator class NeighborlistCalculator: """For integration with .utilities.Data For each image fed to calculate, a list of neighbors with offset distances is returned. Parameters ---------- cutoff : object or float Cutoff function, typically from amp.descriptor.cutoffs. Can be also fed as a float representing the radius above which neighbor interactions are ignored; in this case a cosine cutoff function will be employed. Default is a 6.5-Angstrom cosine cutoff. """ def __init__(self, cutoff): self.globals = Parameters({'cutoff': cutoff}) self.keyed = Parameters() self.parallel_command = 'calculate_neighborlists' def calculate(self, image, key): """For integration with .utilities.Data For each image fed to calculate, a list of neighbors with offset distances is returned. Parameters ---------- image : object ASE atoms object. key : str Key of the image after being hashed. """ cutoff = self.globals.cutoff n = NeighborList(cutoffs=[cutoff / 2.] * len(image), self_interaction=False, bothways=True, skin=0.) n.update(image) return [n.get_neighbors(index) for index in range(len(image))] class FingerprintCalculator: """For integration with .utilities.Data""" def __init__(self, neighborlist, Gs, nmax, cutoff, fortran): self.globals = Parameters({'cutoff': cutoff, 'Gs': Gs, 'nmax': nmax}) self.keyed = Parameters({'neighborlist': neighborlist}) self.parallel_command = 'calculate_fingerprints' self.fortran = fortran self.cutoff = cutoff try: # for scipy v <= 0.90 from scipy import factorial as fac except ImportError: try: # for scipy v >= 0.10 from scipy.misc import factorial as fac except ImportError: # for newer version of scipy from scipy.special import factorial as fac self.factorial = [fac(0.5 * _) for _ in range(4 * nmax + 3)] def calculate(self, image, key): """Makes a list of fingerprints, one per atom, for the fed image. Parameters ---------- image : object ASE atoms object. key : str Key of the image after being hashed. """ nl = self.keyed.neighborlist[key] fingerprints = [] for atom in image: symbol = atom.symbol index = atom.index neighbors, offsets = nl[index] neighborsymbols = [image[_].symbol for _ in neighbors] Rs = [image.positions[neighbor] + np.dot(offset, image.cell) for (neighbor, offset) in zip(neighbors, offsets)] self.atoms = image indexfp = self.get_fingerprint(index, symbol, neighborsymbols, Rs) fingerprints.append(indexfp) return fingerprints def get_fingerprint(self, index, symbol, n_symbols, Rs): """Returns the fingerprint of symmetry function values for atom specified by its index and symbol. n_symbols and Rs are lists of neighbors' symbols and Cartesian positions, respectively. Parameters ---------- index : int Index of the center atom. symbol : str Symbol of the center atom. n_symbols : list of str List of neighbors' symbols. Rs : list of list of float List of Cartesian atomic positions of neighbors. Returns ------- symbols, fingerprints : list of float Fingerprints for atom specified by its index and symbol. """ home = self.atoms[index].position cutoff = self.cutoff Rc = cutoff['kwargs']['Rc'] if cutoff['name'] == 'Cosine': cutoff_fxn = Cosine(Rc) elif cutoff['name'] == 'Polynomial': p_gamma = cutoff['kwargs']['gamma'] cutoff_fxn = Polynomial(Rc, gamma=p_gamma) fingerprint = [] for n in range(self.globals.nmax + 1): for l in range(n + 1): if (n - l) % 2 == 0: norm = 0. for m in range(l + 1): c_nlm = 0. for n_symbol, neighbor in zip(n_symbols, Rs): x = (neighbor[0] - home[0]) / Rc y = (neighbor[1] - home[1]) / Rc z = (neighbor[2] - home[2]) / Rc rho = np.linalg.norm([x, y, z]) if self.fortran: c_args = [Rc * rho] if cutoff['name'] == 'Polynomial': c_args.append(p_gamma) Z_nlm = fmodules.calculate_z( n=n, l=l, m=m, x=x, y=y, z=z, factorial=self.factorial, length=len(self.factorial)) Z_nlm = self.globals.Gs[symbol][n_symbol] * \ Z_nlm * cutoff_fxn(*c_args) else: # Alternative ways to calculate Z_nlm # Z_nlm = self.globals.Gs[symbol][n_symbol] * \ # calculate_Z(n, l, m, x, y, z, # self.factorial) * \ # cutoff_fxn(rho * Rc) # Z_nlm = self.globals.Gs[symbol][n_symbol] * \ # calculate_Z2(n, l, m, x, y, z) * \ # cutoff_fxn(rho * Rc) if rho > 0.: theta = np.arccos(z / rho) else: theta = 0. if x < 0.: phi = np.pi + np.arctan(y / x) elif 0. < x and y < 0.: phi = 2 * np.pi + np.arctan(y / x) elif 0. < x and 0. <= y: phi = np.arctan(y / x) elif x == 0. and 0. < y: phi = 0.5 * np.pi elif x == 0. and y < 0.: phi = 1.5 * np.pi else: phi = 0. c_args = [Rc * rho] if cutoff['name'] == 'Polynomial': c_args.append(p_gamma) Z_nlm = self.globals.Gs[symbol][n_symbol] * \ calculate_R(n, l, rho, self.factorial) * \ sph_harm(m, l, phi, theta) * \ cutoff_fxn(*c_args) # sum over neighbors c_nlm += np.conjugate(Z_nlm) # sum over m values if m == 0: norm += c_nlm * np.conjugate(c_nlm) else: norm += 2. * c_nlm * np.conjugate(c_nlm) fingerprint.append(norm.real) return symbol, fingerprint class FingerprintPrimeCalculator: """For integration with .utilities.Data""" def __init__(self, neighborlist, Gs, nmax, cutoff, fortran): self.globals = Parameters({'cutoff': cutoff, 'Gs': Gs, 'nmax': nmax}) self.keyed = Parameters({'neighborlist': neighborlist}) self.parallel_command = 'calculate_fingerprint_primes' self.fortran = fortran try: # for scipy v <= 0.90 from scipy import factorial as fac except ImportError: try: # for scipy v >= 0.10 from scipy.misc import factorial as fac except ImportError: # for newer version of scipy from scipy.special import factorial as fac self.factorial = [fac(0.5 * _) for _ in range(4 * nmax + 3)] def calculate(self, image, key): """Makes a list of fingerprint derivatives, one per atom, for the fed image. Parameters --------- image : object ASE atoms object. key : str Key of the image after being hashed. """ self.atoms = image nl = self.keyed.neighborlist[key] fingerprintprimes = {} for atom in image: selfsymbol = atom.symbol selfindex = atom.index selfneighborindices, selfneighboroffsets = nl[selfindex] selfneighborsymbols = [ image[_].symbol for _ in selfneighborindices] for i in range(3): # Calculating derivative of self atom fingerprints w.r.t. # coordinates of itself. nneighborindices, nneighboroffsets = nl[selfindex] nneighborsymbols = [image[_].symbol for _ in nneighborindices] Rs = [image.positions[_index] + np.dot(_offset, image.get_cell()) for _index, _offset in zip(nneighborindices, nneighboroffsets)] der_indexfp = self.get_fingerprintprime( selfindex, selfsymbol, nneighborindices, nneighborsymbols, Rs, selfindex, i) fingerprintprimes[ (selfindex, selfsymbol, selfindex, selfsymbol, i)] = \ der_indexfp # Calculating derivative of neighbor atom fingerprints w.r.t. # coordinates of self atom. for nindex, nsymbol, noffset in \ zip(selfneighborindices, selfneighborsymbols, selfneighboroffsets): # for calculating forces, summation runs over neighbor # atoms of type II (within the main cell only) if noffset.all() == 0: nneighborindices, nneighboroffsets = nl[nindex] nneighborsymbols = \ [image[_].symbol for _ in nneighborindices] Rs = [image.positions[_index] + np.dot(_offset, image.get_cell()) for _index, _offset in zip(nneighborindices, nneighboroffsets)] # for calculating derivatives of fingerprints, # summation runs over neighboring atoms of type # I (either inside or outside the main cell) der_indexfp = self.get_fingerprintprime( nindex, nsymbol, nneighborindices, nneighborsymbols, Rs, selfindex, i) fingerprintprimes[ (selfindex, selfsymbol, nindex, nsymbol, i)] = \ der_indexfp return fingerprintprimes def get_fingerprintprime(self, index, symbol, n_indices, n_symbols, Rs, p, q): """Returns the value of the derivative of G for atom with index and symbol with respect to coordinate x_{i} of atom index m. n_indices, n_symbols and Rs are lists of neighbors' indices, symbols and Cartesian positions, respectively. Parameters ---------- index : int Index of the center atom. symbol : str Symbol of the center atom. n_indices : list of int List of neighbors' indices. n_symbols : list of str List of neighbors' symbols. Rs : list of list of float List of Cartesian atomic positions. p : int Index of the pair atom. q : int Direction of the derivative; is an integer from 0 to 2. Returns ------- fingerprint_prime : list of float The value of the derivative of the fingerprints for atom with index and symbol with respect to coordinate x_{i} of atom index m. """ home = self.atoms[index].position cutoff = self.globals.cutoff Rc = cutoff['kwargs']['Rc'] if cutoff['name'] is 'Cosine': cutoff_fxn = Cosine(Rc) elif cutoff['name'] is 'Polynomial': p_gamma = cutoff['kwargs']['gamma'] cutoff_fxn = Polynomial(Rc, gamma=p_gamma) fingerprint_prime = [] for n in range(self.globals.nmax + 1): for l in range(n + 1): if (n - l) % 2 == 0: if self.fortran: # fortran version; faster G_numbers = [self.globals.Gs[symbol][elm] for elm in n_symbols] numbers = [atomic_numbers[elm] for elm in n_symbols] if len(Rs) == 0: norm_prime = 0. else: args_calculate_zernike_prime = dict( n=n, l=l, n_length=len(n_indices), n_indices=list(n_indices), numbers=numbers, rs=Rs, g_numbers=G_numbers, cutoff=Rc, cutofffn=cutoff['name'], indexx=index, home=home, p=p, q=q, fac_length=len(self.factorial), factorial=self.factorial) if cutoff['name'] == 'Polynomial': args_calculate_zernike_prime['p_gamma'] =\ cutoff['kwargs']['gamma'] norm_prime = \ fmodules.calculate_zernike_prime( **args_calculate_zernike_prime) else: norm_prime = 0. for m in range(l + 1): c_nlm = 0. c_nlm_prime = 0. for n_index, n_symbol, neighbor in zip(n_indices, n_symbols, Rs): x = (neighbor[0] - home[0]) / Rc y = (neighbor[1] - home[1]) / Rc z = (neighbor[2] - home[2]) / Rc rho = np.linalg.norm([x, y, z]) c_args = [rho * Rc] if cutoff['name'] == 'Polynomial': c_args.append(p_gamma) _Z_nlm = calculate_Z(n, l, m, x, y, z, self.factorial) # Calculates Z_nlm Z_nlm = _Z_nlm * \ cutoff_fxn(*c_args) # Calculates Z_nlm_prime Z_nlm_prime = _Z_nlm * \ cutoff_fxn.prime(*c_args) * \ der_position( index, n_index, home, neighbor, p, q) _Z_nlm_prime = calculate_Z_prime( n, l, m, x, y, z, q, self.factorial) if (Kronecker(n_index, p) - Kronecker(index, p)) == 1: Z_nlm_prime += \ cutoff_fxn(*c_args) * \ _Z_nlm_prime / Rc elif (Kronecker(n_index, p) - Kronecker(index, p)) == -1: Z_nlm_prime -= \ cutoff_fxn(*c_args) * \ _Z_nlm_prime / Rc # sum over neighbors c_nlm += self.globals.Gs[symbol][ n_symbol] * np.conjugate(Z_nlm) c_nlm_prime += self.globals.Gs[symbol][ n_symbol] * np.conjugate(Z_nlm_prime) # sum over m values if m == 0: norm_prime += 2. * c_nlm * \ np.conjugate(c_nlm_prime) else: norm_prime += 4. * c_nlm * \ np.conjugate(c_nlm_prime) fingerprint_prime.append(norm_prime.real) return fingerprint_prime # Auxiliary functions ######################################################### def binomial(n, k, factorial): """ Returns C(n,k) = n!/(k!(n-k)!). """ assert n >= 0 and k >= 0 and n >= k, \ 'n and k should be non-negative integers with n >= k.' c = factorial[int(2 * n)] / \ (factorial[int(2 * k)] * factorial[int(2 * (n - k))]) return c def calculate_R(n, l, rho, factorial): """Calculates R_{n}^{l}(rho) according to the last equation of wikipedia. """ if (n - l) % 2 != 0: return 0 else: value = 0. k = (n - l) / 2 term1 = np.sqrt(2. * n + 3.) for s in range(int(k) + 1): b1 = binomial(k, s, factorial) b2 = binomial(n - s - 1 + 1.5, k, factorial) value += ((-1) ** s) * b1 * b2 * (rho ** (n - 2. * s)) value *= term1 return value def generate_coefficients(elements): """Automatically generates coefficients if not given by the user. Parameters ---------- elements : list of str List of symbols of all atoms. Returns ------- G : dict of dicts """ _G = {} for element in elements: _G[element] = atomic_numbers[element] G = {} for element in elements: G[element] = _G return G def Kronecker(i, j): """Kronecker delta function. i : int First index of Kronecker delta. j : int Second index of Kronecker delta. Returns ------- Kronecker delta : int """ if i == j: return 1 else: return 0 def der_position(m, n, Rm, Rn, l, i): """Returns the derivative of the norm of position vector R_{mn} with respect to x_{i} of atomic index l. Parameters ---------- m : int Index of the first atom. n : int Index of the second atom. Rm : float Position of the first atom. Rn : float Position of the second atom. l : int Index of the atom force is acting on. i : int Direction of force. Returns ------- der_position : list of float The derivative of the norm of position vector R_{mn} with respect to x_{i} of atomic index l. """ Rmn = np.linalg.norm(Rm - Rn) # mm != nn is necessary for periodic systems if l == m and m != n: der_position = (Rm[i] - Rn[i]) / Rmn elif l == n and m != n: der_position = -(Rm[i] - Rn[i]) / Rmn else: der_position = 0. return der_position def calculate_q(nu, k, l, factorial): """Calculates q_{kl}^{nu} according to the unnumbered equation afer Eq. (7) of "3D Zernike Descriptors for Content Based Shape Retrieval", Computer-Aided Design 36 (2004) 1047-1062. """ result = ((-1) ** (k + nu)) * sqrt((2. * l + 4. * k + 3.) / 3.) * \ binomial(k, nu, factorial) * \ binomial(2. * k, k, factorial) * \ binomial(2. * (k + l + nu) + 1., 2. * k, factorial) / \ binomial(k + l + nu, k, factorial) / (2. ** (2. * k)) return result def calculate_Z(n, l, m, x, y, z, factorial): """Calculates Z_{nl}^{m}(x, y, z) according to the unnumbered equation afer Eq. (11) of "3D Zernike Descriptors for Content Based Shape Retrieval", Computer-Aided Design 36 (2004) 1047-1062. """ value = 0. term1 = sqrt((2. * l + 1.) * factorial[int(2 * (l + m))] * factorial[int(2 * (l - m))]) / factorial[int(2 * l)] term2 = 2. ** (-m) k = int((n - l) / 2.) for nu in range(k + 1): q = calculate_q(nu, k, l, factorial) for alpha in range(nu + 1): b1 = binomial(nu, alpha, factorial) for beta in range(nu - alpha + 1): b2 = binomial(nu - alpha, beta, factorial) term3 = q * b1 * b2 for u in range(m + 1): b5 = binomial(m, u, factorial) term4 = ((-1.)**(m - u)) * b5 * (1j**u) for mu in range(int((l - m) / 2.) + 1): b6 = binomial(l, mu, factorial) b7 = binomial(l - mu, m + mu, factorial) term5 = ((-1.)**mu) * (2.**(-2. * mu)) * b6 * b7 for eta in range(mu + 1): r = 2. * (eta + alpha) + u s = 2. * (mu - eta + beta) + m - u t = 2. * (nu - alpha - beta - mu) + l - m value += term3 * term4 * term5 * \ binomial(mu, eta, factorial) * \ (x ** r) * (y ** s) * (z ** t) term6 = (1j) ** m value = term1 * term2 * term6 * value value = value / sqrt(4. * np.pi / 3.) return value def calculate_Z_prime(n, l, m, x, y, z, p, factorial): """Calculates dZ_{nl}^{m}(x, y, z)/dR_{p} according to the unnumbered equation afer Eq. (11) of "3D Zernike Descriptors for Content Based Shape Retrieval", Computer-Aided Design 36 (2004) 1047-1062. """ value = 0. term1 = sqrt((2. * l + 1.) * factorial[int(2 * (l + m))] * factorial[int(2 * (l - m))]) / factorial[int(2 * l)] term2 = 2. ** (-m) k = int((n - l) / 2.) for nu in range(k + 1): q = calculate_q(nu, k, l, factorial) for alpha in range(nu + 1): b1 = binomial(nu, alpha, factorial) for beta in range(nu - alpha + 1): b2 = binomial(nu - alpha, beta, factorial) term3 = q * b1 * b2 for u in range(m + 1): term4 = ((-1.)**(m - u)) * binomial( m, u, factorial) * (1j**u) for mu in range(int((l - m) / 2.) + 1): term5 = ((-1.)**mu) * (2.**(-2. * mu)) * \ binomial(l, mu, factorial) * \ binomial(l - mu, m + mu, factorial) for eta in range(mu + 1): r = 2 * (eta + alpha) + u s = 2 * (mu - eta + beta) + m - u t = 2 * (nu - alpha - beta - mu) + l - m coefficient = term3 * term4 * \ term5 * binomial(mu, eta, factorial) if p == 0: if r != 0: value += coefficient * r * \ (x ** (r - 1)) * (y ** s) * (z ** t) elif p == 1: if s != 0: value += coefficient * s * \ (x ** r) * (y ** (s - 1)) * (z ** t) elif p == 2: if t != 0: value += coefficient * t * \ (x ** r) * (y ** s) * (z ** (t - 1)) term6 = (1j) ** m value = term1 * term2 * term6 * value value = value / sqrt(4. * np.pi / 3.) return value if __name__ == "__main__": """Directly calling this module; apparently from another node. Calls should come as python -m amp.descriptor.example id hostname:port This session will then start a zmq session with that socket, labeling itself with id. Instructions on what to do will come from the socket. """ import sys import tempfile import zmq from ..utilities import MessageDictionary fortran = False if fmodules is None else True hostsocket = sys.argv[-1] proc_id = sys.argv[-2] msg = MessageDictionary(proc_id) # Send standard lines to stdout signaling process started and where # error is directed. This should be caught by pxssh. (This could # alternatively be done by zmq, but this works.) print('') # Signal that program started. sys.stderr = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.stderr') print('Log and error written to %s' % sys.stderr.name) def w(text): """Writes to stderr and flushes.""" sys.stderr.write(text + '\n') sys.stderr.flush() # Establish client session via zmq; find purpose. context = zmq.Context() w('Context started.') socket = context.socket(zmq.REQ) w('Socket started.') socket.connect('tcp://%s' % hostsocket) w('Connection made.') socket.send_pyobj(msg('')) w('Message sent.') purpose = socket.recv_pyobj() w('Purpose received: {}.'.format(purpose)) if purpose == 'calculate_neighborlists': # Request variables. socket.send_pyobj(msg('', 'cutoff')) cutoff = socket.recv_pyobj() socket.send_pyobj(msg('', 'images')) images = socket.recv_pyobj() # sys.stderr.write(str(images)) # Just to see if they are there. # Perform the calculations. calc = NeighborlistCalculator(cutoff=cutoff) neighborlist = {} # for key in images.iterkeys(): while len(images) > 0: key, image = images.popitem() # Reduce memory. neighborlist[key] = calc.calculate(image, key) # Send the results. socket.send_pyobj(msg('', neighborlist)) socket.recv_string() # Needed to complete REQ/REP. elif purpose == 'calculate_fingerprints': # Request variables. socket.send_pyobj(msg('', 'cutoff')) cutoff = socket.recv_pyobj() socket.send_pyobj(msg('', 'Gs')) Gs = socket.recv_pyobj() socket.send_pyobj(msg('', 'nmax')) nmax = socket.recv_pyobj() socket.send_pyobj(msg('', 'neighborlist')) neighborlist = socket.recv_pyobj() socket.send_pyobj(msg('', 'images')) images = socket.recv_pyobj() w('Received images and parameters.') calc = FingerprintCalculator(neighborlist, Gs, nmax, cutoff, fortran) w('Established calculator. Calculating.') result = {} while len(images) > 0: key, image = images.popitem() # Reduce memory. result[key] = calc.calculate(image, key) if len(images) % 100 == 0: socket.send_pyobj(msg('', len(images))) socket.recv_string() # Needed to complete REQ/REP. # Send the results. w('Sending results.') socket.send_pyobj(msg('', result)) socket.recv_string() # Needed to complete REQ/REP. elif purpose == 'calculate_fingerprint_primes': # Request variables. socket.send_pyobj(msg('', 'cutoff')) cutoff = socket.recv_pyobj() socket.send_pyobj(msg('', 'Gs')) Gs = socket.recv_pyobj() socket.send_pyobj(msg('', 'nmax')) nmax = socket.recv_pyobj() socket.send_pyobj(msg('', 'neighborlist')) neighborlist = socket.recv_pyobj() socket.send_pyobj(msg('', 'images')) images = socket.recv_pyobj() calc = FingerprintPrimeCalculator(neighborlist, Gs, nmax, cutoff, fortran) result = {} while len(images) > 0: key, image = images.popitem() # Reduce memory. result[key] = calc.calculate(image, key) if len(images) % 100 == 0: socket.send_pyobj(msg('', len(images))) socket.recv_string() # Needed to complete REQ/REP. # Send the results. socket.send_pyobj(msg('', result)) socket.recv_string() # Needed to complete REQ/REP. else: raise NotImplementedError('purpose %s unknown.' % purpose) amp-0.6/amp/descriptor/Makefile0000644000175000017500000000007113137634440016412 0ustar muammarmuammarcutoffs.mod: gfortran -c cutoffs.f90 cp cutoffs.mod .. amp-0.6/amp/descriptor/cutoffs.py0000644000175000017500000000743113137634440017004 0ustar muammarmuammar#!/usr/bin/env python """ This script contains different cutoff function forms. Note all cutoff functions need to have a "todict" method to support saving/loading as an Amp object. All cutoff functions also need to have an `Rc` attribute which is the maximum distance at which properties are calculated; this will be used in calculating neighborlists. """ import numpy as np def dict2cutoff(dct): """This function converts a dictionary (which was created with the to_dict method of one of the cutoff classes) into an instantiated version of the class. Modeled after ASE's dict2constraint function. """ if len(dct) != 2: raise RuntimeError('Cutoff dictionary must have only two values,' ' "name" and "kwargs".') return globals()[dct['name']](**dct['kwargs']) class Cosine(object): """Cosine functional form suggested by Behler. Parameters --------- Rc : float Radius above which neighbor interactions are ignored. """ def __init__(self, Rc): self.Rc = Rc def __call__(self, Rij): """ Parameters ---------- Rij : float Distance between pair atoms. Returns ------- float The value of the cutoff function. """ if Rij > self.Rc: return 0. else: return 0.5 * (np.cos(np.pi * Rij / self.Rc) + 1.) def prime(self, Rij): """Derivative of the Cosine cutoff function. Parameters ---------- Rij : float Distance between pair atoms. Returns ------- float The value of derivative of the cutoff function. """ if Rij > self.Rc: return 0. else: return -0.5 * np.pi / self.Rc * np.sin(np.pi * Rij / self.Rc) def todict(self): return {'name': 'Cosine', 'kwargs': {'Rc': self.Rc}} def __repr__(self): return ('' % self.Rc) class Polynomial(object): """Polynomial functional form suggested by Khorshidi and Peterson. Parameters ---------- gamma : float The power of polynomial. Rc : float Radius above which neighbor interactions are ignored. """ def __init__(self, Rc, gamma=4): self.gamma = gamma self.Rc = Rc def __call__(self, Rij): """ Parameters ---------- Rij : float Distance between pair atoms. Returns ------- value : float The value of the cutoff function. """ if Rij > self.Rc: return 0. else: value = 1. + self.gamma * (Rij / self.Rc) ** (self.gamma + 1) - \ (self.gamma + 1) * (Rij / self.Rc) ** self.gamma return value def prime(self, Rij): """Derivative of the Polynomial cutoff function. Parameters ---------- Rij : float Distance between pair atoms. Returns ------- float The value of derivative of the cutoff function. """ if Rij > self.Rc: return 0. else: value = (self.gamma * (self.gamma + 1) / self.Rc) * \ ((Rij / self.Rc) ** self.gamma - (Rij / self.Rc) ** (self.gamma - 1)) return value def todict(self): return {'name': 'Polynomial', 'kwargs': {'Rc': self.Rc, 'gamma': self.gamma } } def __repr__(self): return ('' % (self.Rc, self.gamma)) amp-0.6/amp/descriptor/bispectrum.py0000644000175000017500000006226613137634440017517 0ustar muammarmuammarimport numpy as np from numpy import cos, sqrt, exp from ase.data import atomic_numbers from ase.calculators.calculator import Parameters from ..utilities import Data, Logger, importer from .cutoffs import Cosine, dict2cutoff NeighborList = importer('NeighborList') class Bispectrum(object): """Class that calculates spherical harmonic bispectrum fingerprints. Parameters ---------- cutoff : object or float Cutoff function, typically from amp.descriptor.cutoffs. Can be also fed as a float representing the radius above which neighbor interactions are ignored; in this case a cosine cutoff function will be employed. Default is a 6.5-Angstrom cosine cutoff. Gs : dict Dictionary of symbols and dictionaries for making fingerprints. Either auto-genetrated, or given in the following form, for example: >>> Gs = {"Au": {"Au": 3., "O": 2.}, "O": {"Au": 5., "O": 10.}} jmax : integer or half-integer or dict Maximum degree of spherical harmonics that will be included in the fingerprint vector. Can be also fed as a dictionary with chemical species as keys. dblabel : str Optional separate prefix/location for database files, including fingerprints, fingerprint derivatives, and neighborlists. This file location can be shared between calculator instances to avoid re-calculating redundant information. If not supplied, just uses the value from label. elements : list List of allowed elements present in the system. If not provided, will be found automatically. version : str Version of fingerprints. Raises: ------- RuntimeError, TypeError """ def __init__(self, cutoff=Cosine(6.5), Gs=None, jmax=5, dblabel=None, elements=None, version='2016.02', mode='atom-centered'): # Check of the version of descriptor, particularly if restarting. compatibleversions = ['2016.02', ] if (version is not None) and version not in compatibleversions: raise RuntimeError('Error: Trying to use bispectrum fingerprints' ' version %s, but this module only supports' ' versions %s. You may need an older or ' ' newer version of Amp.' % (version, compatibleversions)) else: version = compatibleversions[-1] # Check that the mode is atom-centered. if mode != 'atom-centered': raise RuntimeError('Bispectrum scheme only works ' 'in atom-centered mode. %s ' 'specified.' % mode) # If the cutoff is provided as a number, Cosine function will be used # by default. if isinstance(cutoff, int) or isinstance(cutoff, float): cutoff = Cosine(cutoff) # If the cutoff is provided as a dictionary, assume we need to load it # with dict2cutoff. if type(cutoff) is dict: cutoff = dict2cutoff(cutoff) # The parameters dictionary contains the minimum information # to produce a compatible descriptor; that is, one that gives # an identical fingerprint when fed an ASE image. p = self.parameters = Parameters( {'importname': '.descriptor.bispectrum.Bispectrum', 'mode': 'atom-centered'}) p.version = version p.cutoff = cutoff.todict() p.Gs = Gs p.jmax = jmax p.elements = elements self.dblabel = dblabel self.parent = None # Can hold a reference to main Amp instance. def tostring(self): """Returns an evaluatable representation of the calculator that can be used to restart the calculator.""" return self.parameters.tostring() def calculate_fingerprints(self, images, parallel=None, log=None, calculate_derivatives=False): """Calculates the fingerpints of the images, for the ones not already done. Parameters ---------- images : list or str List of ASE atoms objects with positions, symbols, energies, and forces in ASE format. This is the training set of data. This can also be the path to an ASE trajectory (.traj) or database (.db) file. Energies can be obtained from any reference, e.g. DFT calculations. parallel : dict Configuration for parallelization. Should be in same form as in amp.Amp. log : Logger object Write function at which to log data. Note this must be a callable function. calculate_derivatives : bool Decides whether or not fingerprintprimes should also be calculated. """ if parallel is None: parallel = {'cores': 1} if calculate_derivatives is True: import warnings warnings.warn('Zernike descriptor cannot train forces yet. ' 'Force training automatically turnned off. ') calculate_derivatives = False log = Logger(file=None) if log is None else log if (self.dblabel is None) and hasattr(self.parent, 'dblabel'): self.dblabel = self.parent.dblabel self.dblabel = 'amp-data' if self.dblabel is None else self.dblabel p = self.parameters log('Cutoff function: %s' % repr(dict2cutoff(p.cutoff))) if p.elements is None: log('Finding unique set of elements in training data.') p.elements = set([atom.symbol for atoms in images.values() for atom in atoms]) p.elements = sorted(p.elements) log('%i unique elements included: ' % len(p.elements) + ', '.join(p.elements)) log('Maximum degree of spherical harmonic bispectrum:') if isinstance(p.jmax, dict): for _ in p.jmax.keys(): log(' %2s: %d' % (_, p.jmax[_])) else: log('jmax: %d' % p.jmax) if p.Gs is None: log('No coefficient for atomic density function supplied; ' 'creating defaults.') p.Gs = generate_coefficients(p.elements) log('Coefficients of atomic density function for each element:') for _ in p.Gs.keys(): log(' %2s: %s' % (_, str(p.Gs[_]))) # Counts the number of descriptors for each element. no_of_descriptors = {} for element in p.elements: count = 0 if isinstance(p.jmax, dict): for _2j1 in range(int(2 * p.jmax[element]) + 1): for j in range(int(min(_2j1, p.jmax[element])) + 1): count += 1 else: for _2j1 in range(int(2 * p.jmax) + 1): for j in range(int(min(_2j1, p.jmax)) + 1): count += 1 no_of_descriptors[element] = count log('Number of descriptors for each element:') for element in p.elements: log(' %2s: %d' % (element, no_of_descriptors.pop(element))) log('Calculating neighborlists...', tic='nl') if not hasattr(self, 'neighborlist'): calc = NeighborlistCalculator(cutoff=p.cutoff['kwargs']['Rc']) self.neighborlist = Data(filename='%s-neighborlists' % self.dblabel, calculator=calc) self.neighborlist.calculate_items(images, parallel=parallel, log=log) log('...neighborlists calculated.', toc='nl') log('Fingerprinting images...', tic='fp') if not hasattr(self, 'fingerprints'): calc = FingerprintCalculator(neighborlist=self.neighborlist, Gs=p.Gs, jmax=p.jmax, cutoff=p.cutoff,) self.fingerprints = Data(filename='%s-fingerprints' % self.dblabel, calculator=calc) self.fingerprints.calculate_items(images, parallel=parallel, log=log) log('...fingerprints calculated.', toc='fp') # Calculators ################################################################# # Neighborlist Calculator class NeighborlistCalculator: """For integration with .utilities.Data For each image fed to calculate, a list of neighbors with offset distances is returned. """ def __init__(self, cutoff): self.globals = Parameters({'cutoff': cutoff}) self.keyed = Parameters() self.parallel_command = 'calculate_neighborlists' def calculate(self, image, key): cutoff = self.globals.cutoff n = NeighborList(cutoffs=[cutoff / 2.] * len(image), self_interaction=False, bothways=True, skin=0.) n.update(image) return [n.get_neighbors(index) for index in range(len(image))] class FingerprintCalculator: """For integration with .utilities.Data """ def __init__(self, neighborlist, Gs, jmax, cutoff,): self.globals = Parameters({'cutoff': cutoff, 'Gs': Gs, 'jmax': jmax}) self.keyed = Parameters({'neighborlist': neighborlist}) self.parallel_command = 'calculate_fingerprints' self.factorial = [1] for _ in range(int(3. * jmax) + 2): if _ > 0: self.factorial += [_ * self.factorial[_ - 1]] def calculate(self, image, key): """Makes a list of fingerprints, one per atom, for the fed image. Parameters ---------- image : object ASE atoms object. key : str key of the image after being hashed. """ nl = self.keyed.neighborlist[key] fingerprints = [] for atom in image: symbol = atom.symbol index = atom.index neighbors, offsets = nl[index] neighborsymbols = [image[_].symbol for _ in neighbors] Rs = [image.positions[neighbor] + np.dot(offset, image.cell) for (neighbor, offset) in zip(neighbors, offsets)] self.atoms = image indexfp = self.get_fingerprint(index, symbol, neighborsymbols, Rs) fingerprints.append(indexfp) return fingerprints def get_fingerprint(self, index, symbol, n_symbols, Rs): """Returns the fingerprint of symmetry function values for atom specified by its index and symbol. n_symbols and Rs are lists of neighbors' symbols and Cartesian positions, respectively. Parameters ---------- index : int Index of the center atom. symbol : str Symbol of the center atom. n_symbols : list of str List of neighbors' symbols. Rs : list of list of float List of Cartesian atomic positions of neighbors. Returns ------- symbols, fingerprints : list of float fingerprints for atom specified by its index and symbol. """ home = self.atoms[index].position cutoff = self.globals.cutoff Rc = cutoff['kwargs']['Rc'] jmax = self.globals.jmax if cutoff['name'] == 'Cosine': cutoff_fxn = Cosine(Rc) elif cutoff['name'] == 'Polynomial': # cutoff_fxn = Polynomial(cutoff) raise NotImplementedError() rs = [] psis = [] thetas = [] phis = [] for neighbor in Rs: x = neighbor[0] - home[0] y = neighbor[1] - home[1] z = neighbor[2] - home[2] r = np.linalg.norm(neighbor - home) if r > 10.**(-10.): psi = np.arcsin(r / Rc) theta = np.arccos(z / r) if abs((z / r) - 1.0) < 10.**(-8.): theta = 0.0 elif abs((z / r) + 1.0) < 10.**(-8.): theta = np.pi if x < 0.: phi = np.pi + np.arctan(y / x) elif 0. < x and y < 0.: phi = 2 * np.pi + np.arctan(y / x) elif 0. < x and 0. <= y: phi = np.arctan(y / x) elif x == 0. and 0. < y: phi = 0.5 * np.pi elif x == 0. and y < 0.: phi = 1.5 * np.pi else: phi = 0. rs += [r] psis += [psi] thetas += [theta] phis += [phi] fingerprint = [] for _2j1 in range(int(2 * jmax) + 1): j1 = 0.5 * _2j1 j2 = 0.5 * _2j1 for j in range(int(min(_2j1, jmax)) + 1): value = calculate_B(j1, j2, 1.0 * j, self.globals.Gs[symbol], Rc, cutoff['name'], self.factorial, n_symbols, rs, psis, thetas, phis) value = value.real fingerprint.append(value) return symbol, fingerprint # Auxiliary functions ######################################################### def calculate_B(j1, j2, j, G_element, cutoff, cutofffn, factorial, n_symbols, rs, psis, thetas, phis): """Calculates bi-spectrum B_{j1, j2, j} according to Eq. (5) of "Gaussian Approximation Potentials: The Accuracy of Quantum Mechanics, without the Electrons", Phys. Rev. Lett. 104, 136403. """ mvals = m_values(j) B = 0. for m in mvals: for mp in mvals: c = calculate_c(j, mp, m, G_element, cutoff, cutofffn, factorial, n_symbols, rs, psis, thetas, phis) m1bound = min(j1, m + j2) mp1bound = min(j1, mp + j2) m1 = max(-j1, m - j2) while m1 < (m1bound + 0.5): mp1 = max(-j1, mp - j2) while mp1 < (mp1bound + 0.5): c1 = calculate_c(j1, mp1, m1, G_element, cutoff, cutofffn, factorial, n_symbols, rs, psis, thetas, phis) c2 = calculate_c(j2, mp - mp1, m - m1, G_element, cutoff, cutofffn, factorial, n_symbols, rs, psis, thetas, phis) B += CG(j1, m1, j2, m - m1, j, m, factorial) * \ CG(j1, mp1, j2, mp - mp1, j, mp, factorial) * \ np.conjugate(c) * c1 * c2 mp1 += 1. m1 += 1. return B ############################################################################### def calculate_c(j, mp, m, G_element, cutoff, cutofffn, factorial, n_symbols, rs, psis, thetas, phis): """Calculates c^{j}_{m'm} according to Eq. (4) of "Gaussian Approximation Potentials: The Accuracy of Quantum Mechanics, without the Electrons", Phys. Rev. Lett. 104, 136403 """ if cutofffn is 'Cosine': cutoff_fxn = Cosine(cutoff) elif cutofffn is 'Polynomial': # cutoff_fxn = Polynomial(cutoff) raise NotImplementedError value = 0. for n_symbol, r, psi, theta, phi in zip(n_symbols, rs, psis, thetas, phis): value += G_element[n_symbol] * \ np.conjugate(U(j, m, mp, psi, theta, phi, factorial)) * \ cutoff_fxn(r) return value ############################################################################### def m_values(j): """Returns a list of m values for a given j.""" assert j >= 0, '2*j should be a non-negative integer.' return [j - i for i in range(int(2 * j + 1))] ############################################################################### def binomial(n, k, factorial): """Returns C(n,k) = n!/(k!(n-k)!).""" assert n >= 0 and k >= 0 and n >= k, \ 'n and k should be non-negative integers with n >= k.' c = factorial[int(n)] / (factorial[int(k)] * factorial[int(n - k)]) return c ############################################################################### def WignerD(j, m, mp, alpha, beta, gamma, factorial): """Returns the Wigner-D matrix. alpha, beta, and gamma are the Euler angles.""" result = 0 if abs(beta - np.pi / 2.) < 10.**(-10.): # Varshalovich Eq. (5), Section 4.16, Page 113. # j, m, and mp here are J, M, and M', respectively, in Eq. (5). for k in range(int(2 * j + 1)): if k > j + mp or k > j - m: break elif k < mp - m: continue result += (-1)**k * binomial(j + mp, k, factorial) * \ binomial(j - mp, k + m - mp, factorial) result *= (-1)**(m - mp) * \ sqrt(float(factorial[int(j + m)] * factorial[int(j - m)]) / float((factorial[int(j + mp)] * factorial[int(j - mp)]))) / \ 2.**j result *= exp(-1j * m * alpha) * exp(-1j * mp * gamma) else: # Varshalovich Eq. (10), Section 4.16, Page 113. # m, mpp, and mp here are M, m, and M', respectively, in Eq. (10). mvals = m_values(j) for mpp in mvals: # temp1 = WignerD(j, m, mpp, 0, np.pi/2, 0) = d(j, m, mpp, np.pi/2) temp1 = 0. for k in range(int(2 * j + 1)): if k > j + mpp or k > j - m: break elif k < mpp - m: continue temp1 += (-1)**k * binomial(j + mpp, k, factorial) * \ binomial(j - mpp, k + m - mpp, factorial) temp1 *= (-1)**(m - mpp) * \ sqrt(float(factorial[int(j + m)] * factorial[int(j - m)]) / float((factorial[int(j + mpp)] * factorial[int(j - mpp)]))) / 2.**j # temp2 = WignerD(j, mpp, mp, 0, np.pi/2, 0) = d(j, mpp, mp, # np.pi/2) temp2 = 0. for k in range(int(2 * j + 1)): if k > j - mp or k > j - mpp: break elif k < - mp - mpp: continue temp2 += (-1)**k * binomial(j - mp, k, factorial) * \ binomial(j + mp, k + mpp + mp, factorial) temp2 *= (-1)**(mpp + mp) * \ sqrt(float(factorial[int(j + mpp)] * factorial[int(j - mpp)]) / float((factorial[int(j - mp)] * factorial[int(j + mp)]))) / 2.**j result += temp1 * exp(-1j * mpp * beta) * temp2 # Empirical normalization factor so results match Varshalovich # Tables 4.3-4.12 # Note that this exact normalization does not follow from the # above equations result *= (1j**(2 * j - m - mp)) * ((-1)**(2 * m)) result *= exp(-1j * m * alpha) * exp(-1j * mp * gamma) return result ############################################################################### def U(j, m, mp, omega, theta, phi, factorial): """Calculates rotation matrix U_{MM'}^{J} in terms of rotation angle omega as well as rotation axis angles theta and phi, according to Varshalovich, Eq. (3), Section 4.5, Page 81. j, m, mp, and mpp here are J, M, M', and M'' in Eq. (3). """ result = 0. mvals = m_values(j) for mpp in mvals: result += WignerD(j, m, mpp, phi, theta, -phi, factorial) * \ exp(- 1j * mpp * omega) * \ WignerD(j, mpp, mp, phi, -theta, -phi, factorial) return result ############################################################################### def CG(a, alpha, b, beta, c, gamma, factorial): """Clebsch-Gordan coefficient C_{a alpha b beta}^{c gamma} is calculated acoording to the expression given in Varshalovich Eq. (3), Section 8.2, Page 238.""" if int(2. * a) != 2. * a or int(2. * b) != 2. * b or int(2. * c) != 2. * c: raise ValueError("j values must be integer or half integer") if int(2. * alpha) != 2. * alpha or int(2. * beta) != 2. * beta or \ int(2. * gamma) != 2. * gamma: raise ValueError("m values must be integer or half integer") if alpha + beta - gamma != 0.: return 0. else: minimum = min(a + b - c, a - b + c, -a + b + c, a + b + c + 1., a - abs(alpha), b - abs(beta), c - abs(gamma)) if minimum < 0.: return 0. else: sqrtarg = \ factorial[int(a + alpha)] * \ factorial[int(a - alpha)] * \ factorial[int(b + beta)] * \ factorial[int(b - beta)] * \ factorial[int(c + gamma)] * \ factorial[int(c - gamma)] * \ (2. * c + 1.) * \ factorial[int(a + b - c)] * \ factorial[int(a - b + c)] * \ factorial[int(-a + b + c)] / \ factorial[int(a + b + c + 1.)] sqrtres = sqrt(sqrtarg) zmin = max(a + beta - c, b - alpha - c, 0.) zmax = min(b + beta, a - alpha, a + b - c) sumres = 0. for z in range(int(zmin), int(zmax) + 1): value = \ factorial[int(z)] * \ factorial[int(a + b - c - z)] * \ factorial[int(a - alpha - z)] * \ factorial[int(b + beta - z)] * \ factorial[int(c - b + alpha + z)] * \ factorial[int(c - a - beta + z)] sumres += (-1.)**z / value result = sqrtres * sumres return result ############################################################################### def generate_coefficients(elements): """Automatically generates coefficients if not given by the user. Parameters --------- elements : list of str List of symbols of all atoms. Returns ------- G : dict of dicts """ _G = {} for element in elements: _G[element] = atomic_numbers[element] G = {} for element in elements: G[element] = _G return G ############################################################################### if __name__ == "__main__": """Directly calling this module; apparently from another node. Calls should come as python -m amp.descriptor.example id hostname:port This session will then start a zmq session with that socket, labeling itself with id. Instructions on what to do will come from the socket. """ import sys import tempfile import zmq from ..utilities import MessageDictionary hostsocket = sys.argv[-1] proc_id = sys.argv[-2] msg = MessageDictionary(proc_id) # Send standard lines to stdout signaling process started and where # error is directed. This should be caught by pxssh. (This could # alternatively be done by zmq, but this works.) print('') # Signal that program started. sys.stderr = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.stderr') print('Log and error written to %s' % sys.stderr.name) # Establish client session via zmq; find purpose. context = zmq.Context() socket = context.socket(zmq.REQ) socket.connect('tcp://%s' % hostsocket) socket.send_pyobj(msg('')) purpose = socket.recv_pyobj() if purpose == 'calculate_neighborlists': # Request variables. socket.send_pyobj(msg('', 'cutoff')) cutoff = socket.recv_pyobj() socket.send_pyobj(msg('', 'images')) images = socket.recv_pyobj() # sys.stderr.write(str(images)) # Just to see if they are there. # Perform the calculations. calc = NeighborlistCalculator(cutoff=cutoff) neighborlist = {} # for key in images.iterkeys(): while len(images) > 0: key, image = images.popitem() # Reduce memory. neighborlist[key] = calc.calculate(image, key) # Send the results. socket.send_pyobj(msg('', neighborlist)) socket.recv_string() # Needed to complete REQ/REP. elif purpose == 'calculate_fingerprints': # Request variables. socket.send_pyobj(msg('', 'cutoff')) cutoff = socket.recv_pyobj() socket.send_pyobj(msg('', 'Gs')) Gs = socket.recv_pyobj() socket.send_pyobj(msg('', 'jmax')) jmax = socket.recv_pyobj() socket.send_pyobj(msg('', 'neighborlist')) neighborlist = socket.recv_pyobj() socket.send_pyobj(msg('', 'images')) images = socket.recv_pyobj() calc = FingerprintCalculator(neighborlist, Gs, jmax, cutoff,) result = {} while len(images) > 0: key, image = images.popitem() # Reduce memory. result[key] = calc.calculate(image, key) if len(images) % 100 == 0: socket.send_pyobj(msg('', len(images))) socket.recv_string() # Needed to complete REQ/REP. # Send the results. socket.send_pyobj(msg('', result)) socket.recv_string() # Needed to complete REQ/REP. else: raise NotImplementedError('purpose %s unknown.' % purpose) amp-0.6/amp/descriptor/cutoffs.f900000644000175000017500000000405713137634440016753 0ustar muammarmuammar module cutoffs implicit none contains function cutoff_fxn(r, rc, cutofffn, p_gamma) double precision:: r, rc, pi, cutoff_fxn ! gamma parameter for the polynomial cutoff double precision, optional:: p_gamma character(len=20):: cutofffn ! To avoid noise, for each call of this function, it is better to ! set returned variables to 0.0d0. cutoff_fxn = 0.0d0 if (cutofffn == 'Cosine') then if (r > rc) then cutoff_fxn = 0.0d0 else pi = 4.0d0 * datan(1.0d0) cutoff_fxn = 0.5d0 * (cos(pi*r/rc) + 1.0d0) end if elseif (cutofffn == 'Polynomial') then if (r > rc) then cutoff_fxn = 0.0d0 else cutoff_fxn = 1. + p_gamma & * (r / rc) ** (p_gamma + 1) & - (p_gamma + 1) * (r / rc) ** p_gamma end if endif end function cutoff_fxn function cutoff_fxn_prime(r, rc, cutofffn, p_gamma) double precision:: r, rc, cutoff_fxn_prime, pi ! gamma parameter for the polynomial cutoff double precision, optional:: p_gamma character(len=20):: cutofffn ! To avoid noise, for each call of this function, it is better to ! set returned variables to 0.0d0. cutoff_fxn_prime = 0.0d0 if (cutofffn == 'Cosine') then if (r > rc) then cutoff_fxn_prime = 0.0d0 else pi = 4.0d0 * datan(1.0d0) cutoff_fxn_prime = -0.5d0 * pi * sin(pi*r/rc) / rc end if elseif (cutofffn == 'Polynomial') then if (r > rc) then cutoff_fxn_prime = 0.0d0 else cutoff_fxn_prime = (p_gamma * (p_gamma + 1) / rc) & * ((r / rc) ** p_gamma - (r / rc) ** (p_gamma - 1)) end if end if end function cutoff_fxn_prime end module cutoffs amp-0.6/amp/descriptor/zernike.f900000644000175000017500000003153213137634440016747 0ustar muammarmuammar!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine calculate_zernike_prime(n, l, n_length, n_indices, & numbers, rs, g_numbers, cutoff, indexx, home, p, q, & fac_length, factorial, norm_prime, cutofffn, p_gamma) use cutoffs implicit none integer:: n, l integer:: indexx, p, q, n_length, fac_length integer, dimension(n_length):: n_indices, numbers, g_numbers double precision, dimension(n_length, 3):: rs double precision, dimension(3):: home double precision, dimension(fac_length):: factorial double precision:: cutoff ! gamma parameter for the polynomial cutoff double precision, optional:: p_gamma character(len=20):: cutofffn complex*16:: norm_prime !f2py intent(in):: n, l, n_indices, numbers, g_numbers, rs, p_gamma !f2py intent(in):: home, indexx, p, q, cutoff, n_length, fac_length !f2py intent(out):: norm_prime integer:: m complex*16:: c_nlm, c_nlm_prime, z_nlm_, z_nlm, & z_nlm_prime, z_nlm_prime_ integer:: n_index, n_symbol, iter double precision, dimension(3):: neighbor double precision:: x, y, z, rho norm_prime = (0.0d0, 0.0d0) do m = 0, l c_nlm = (0.0d0, 0.0d0) c_nlm_prime = (0.0d0, 0.0d0) do iter = 1, n_length n_index = n_indices(iter) n_symbol = numbers(iter) neighbor(1) = rs(iter, 1) neighbor(2) = rs(iter, 2) neighbor(3) = rs(iter, 3) x = (neighbor(1) - home(1)) / cutoff y = (neighbor(2) - home(2)) / cutoff z = (neighbor(3) - home(3)) / cutoff rho = (x ** 2.0d0 + y ** 2.0d0 + z ** 2.0d0) ** 0.5d0 call calculate_z(n, l, m, x, y, z, factorial, & fac_length, z_nlm_) ! Calculate z_nlm if (present(p_gamma)) then z_nlm = z_nlm_ * cutoff_fxn(rho * cutoff, & cutoff, cutofffn, p_gamma) ! Calculates z_nlm_prime z_nlm_prime = z_nlm_ * & cutoff_fxn_prime(rho * cutoff, cutoff, & cutofffn, p_gamma) * & der_position(indexx, n_index, home, neighbor, p, q) else z_nlm = z_nlm_ * cutoff_fxn(rho * cutoff, & cutoff, cutofffn) ! Calculates z_nlm_prime z_nlm_prime = z_nlm_ * & cutoff_fxn_prime(rho * cutoff, cutoff, & cutofffn) * & der_position(indexx, n_index, home, neighbor, p, q) endif call calculate_z_prime(n, l, m, x, y, z, q, factorial, & fac_length, z_nlm_prime_) if (kronecker(n_index, p) - & kronecker(indexx, p) == 1) then if (present(p_gamma)) then z_nlm_prime = z_nlm_prime + & cutoff_fxn(rho * cutoff, cutoff, & cutofffn, p_gamma) * z_nlm_prime_ / & cutoff else z_nlm_prime = z_nlm_prime + & cutoff_fxn(rho * cutoff, cutoff, & cutofffn) * z_nlm_prime_ / cutoff end if else if (kronecker(n_index, p) - kronecker(indexx, p) & == -1) then if (present(p_gamma)) then z_nlm_prime = z_nlm_prime - & cutoff_fxn(rho * cutoff, cutoff, & cutofffn, p_gamma) * z_nlm_prime_ / & cutoff else z_nlm_prime = z_nlm_prime - & cutoff_fxn(rho * cutoff, cutoff, & cutofffn) * z_nlm_prime_ / cutoff end if end if ! sum over neighbors c_nlm = c_nlm + g_numbers(iter) * conjg(z_nlm) c_nlm_prime = c_nlm_prime + & g_numbers(iter) * conjg(z_nlm_prime) end do ! sum over m values if (m == 0) then norm_prime = norm_prime + & 2.0d0 * c_nlm * conjg(c_nlm_prime) else norm_prime = norm_prime + & 4.0d0 * c_nlm * conjg(c_nlm_prime) end if enddo CONTAINS function der_position(mm, nn, Rm, Rn, ll, ii) implicit none integer:: mm, nn, ll, ii, xyz double precision, dimension(3):: Rm, Rn, Rmn_ double precision:: der_position, Rmn do xyz = 1, 3 Rmn_(xyz) = Rm(xyz) - Rn(xyz) end do Rmn = sqrt(dot_product(Rmn_, Rmn_)) if ((ll == mm) .AND. (mm /= nn)) then der_position = (Rm(ii + 1) - Rn(ii + 1)) / Rmn else if ((ll == nn) .AND. (mm /= nn)) then der_position = - (Rm(ii + 1) - Rn(ii + 1)) / Rmn else der_position = 0.0d0 end if end function function kronecker(i, j) implicit none integer:: i, j integer:: kronecker if (i == j) then kronecker = 1 else kronecker = 0 end if end function end subroutine calculate_zernike_prime subroutine calculate_z(n, l, m, x, y, z, factorial, length, & output) implicit none integer:: n, l, m, length double precision:: x, y, z double precision, dimension(length):: factorial complex*16:: output, ii, term4, term6 !f2py intent(in):: n, l, m, x, y, z, factorial, length !f2py intent(out):: output integer:: k, nu, alpha, beta, eta, u, mu, r, s, t double precision:: term1, term2, q, b1, b2, term3 double precision:: term5, b5, b6, b7, b8, pi pi = 4.0d0 * datan(1.0d0) output = (0.0d0, 0.0d0) term1 = sqrt((2.0d0 * l + 1.0d0) * & factorial(int(2 * (l + m)) + 1) * & factorial(int(2 * (l - m)) + 1)) / factorial(int(2 * l) + 1) term2 = 2.0d0 ** (-m) ii = (0.0d0, 1.0d0) k = int((n - l) / 2.0d0) do nu = 0, k call calculate_q(nu, k, l, factorial, length, q) do alpha = 0, nu call binomial(float(nu), float(alpha), & factorial, length, b1) do beta = 0, nu - alpha call binomial(float(nu - alpha), float(beta), & factorial, length, b2) term3 = q * b1 * b2 do u = 0, m call binomial(float(m), float(u), factorial, & length, b5) term4 = ((-1.0d0)**(m - u)) * b5 * (ii**u) do mu = 0, int((l - m) / 2.0d0) call binomial(float(l), float(mu), & factorial, length, b6) call binomial(float(l - mu), float(m + mu),& factorial, length, b7) term5 = ((-1.0d0) ** mu) * (2.0d0 ** & (-2.0d0 * mu)) * b6 * b7 do eta = 0, mu call binomial(float(mu), float(eta), & factorial, length, b8) r = 2 * (eta + alpha) + u s = 2 * (mu - eta + beta) + m - u t = 2 * (nu - alpha - beta - mu) + l - m output = output + term3 * term4 & * term5 * b8 * (x ** r) & * (y ** s) * (z ** t) end do end do end do end do end do end do term6 = (ii) ** m output = term1 * term2 * term6 * output output = output / sqrt(4.0d0 * pi / 3.0d0) end subroutine calculate_z subroutine calculate_z_prime(n, l, m, x, y, z, p, factorial, & length, output) implicit none integer:: n, l, m, length, p double precision:: x, y, z double precision, dimension(length):: factorial complex*16:: output, ii, coefficient, term4, term6 !f2py intent(in):: n, l, m, x, y, z, factorial, p, length !f2py intent(out):: output integer:: k, nu, alpha, beta, eta, u, mu, r, s, t double precision:: term1, term2, q, b1, b2, term3 double precision:: term5, b3, b4, b5, b6, pi pi = 4.0d0 * datan(1.0d0) output = (0.0d0, 0.0d0) term1 = sqrt((2.0d0 * l + 1.0d0) * & factorial(int(2 * (l + m)) + 1) * & factorial(int(2 * (l - m)) + 1)) / & factorial(int(2 * l) + 1) term2 = 2.0d0 ** (-m) ii = (0.0d0, 1.0d0) k = int((n - l) / 2.) do nu = 0, k call calculate_q(nu, k, l, factorial, length, q) do alpha = 0, nu call binomial(float(nu), float(alpha), factorial, & length, b1) do beta = 0, nu - alpha call binomial(float(nu - alpha), float(beta), & factorial, length, b2) term3 = q * b1 * b2 do u = 0, m call binomial(float(m), float(u), factorial, length, & b3) term4 = ((-1.0d0)**(m - u)) * b3 * (ii**u) do mu = 0, int((l - m) / 2.) call binomial(float(l), float(mu), factorial, & length, b4) call binomial(float(l - mu), float(m + mu), & factorial, length, b5) term5 = & ((-1.0d0)**mu) * (2.0d0**(-2.0d0 * mu)) * b4 * b5 do eta = 0, mu call binomial(float(mu), float(eta), factorial, & length, b6) r = 2 * (eta + alpha) + u s = 2 * (mu - eta + beta) + m - u t = 2 * (nu - alpha - beta - mu) + l - m coefficient = term3 * term4 * term5 * b6 if (p == 0) then if (r .NE. 0) then output = output + coefficient * r * & (x ** (r - 1)) * (y ** s) * (z ** t) end if else if (p == 1) then if (s .NE. 0) then output = output + coefficient * s * & (x ** r) * (y ** (s - 1)) * (z ** t) end if else if (p == 2) then if (t .NE. 0) then output = output + coefficient * t * & (x ** r) * (y ** s) * (z ** (t - 1)) end if end if end do end do end do end do end do end do term6 = (ii) ** m output = term1 * term2 * term6 * output output = output / sqrt(4.0d0 * pi / 3.0d0) end subroutine calculate_z_prime subroutine calculate_q(nu, k, l, factorial, length, output) implicit none integer:: nu, k, l, length double precision, dimension(length):: factorial double precision:: output, b1, b2, b3, b4 !f2py intent(in):: nu, k, l, factorial !f2py intent(out):: output call binomial(float(k), float(nu), factorial, length, b1) call binomial(float(2 * k), float(k), factorial, length, b2) call binomial(float(2 * (k + l + nu) + 1), float(2 * k), & factorial, length, b3) call binomial(float(k + l + nu), float(k), factorial, & length, b4) output = ((-1.0d0) ** (k + nu)) * & sqrt((2.0d0 * l + 4.0d0 * k + 3.0d0) / 3.0d0) * b1 * b2 * & b3 / b4 / (2.0d0 ** (2.0d0 * k)) end subroutine calculate_q subroutine binomial(n, k, factorial, length, output) implicit none real(4):: n, k integer:: length double precision, dimension(length):: factorial double precision:: output !f2py intent(in):: n, k, factorial, length !f2py intent(out):: output output = factorial(INT(2 * n) + 1) / & factorial(INT(2 * k) + 1) / & factorial(INT(2 * (n - k)) + 1) end subroutine binomial !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! amp-0.6/amp/descriptor/analysis.py0000644000175000017500000000775213137634440017164 0ustar muammarmuammarimport numpy as np from ..utilities import hash_images, get_hash class FingerprintPlot: """Create plots of fingerprint ranges. Initialize with an Amp calculator object. """ def __init__(self, calc): self._calc = calc def __call__(self, images, name='fingerprints.pdf', overlay=None): """Creates a violin plot of fingerprints for each element type in the fed images; saves to specified filename. Optionally, the user can supply either an ase.Atoms or a list of ase.Atom objects with the overlay keyword; this will result in points being added to the fingerprints indicating the values for that atom or atoms object. """ from matplotlib import pyplot from matplotlib.backends.backend_pdf import PdfPages self.compile_fingerprints(images) self.figures = {} for element in self.data.keys(): self.figures[element] = pyplot.figure(figsize=(11., 8.5)) fig = self.figures[element] ax = fig.add_subplot(211) ax.violinplot(self.data[element]) ax.set_ylabel('raw value') ax.set_xlim([0, self.data[element].shape[1] + 1]) if hasattr(self._calc.model.parameters, 'fprange'): ax2 = fig.add_subplot(212) fprange = self._calc.model.parameters.fprange[element] fprange = np.array(fprange) fprange.transpose() d = self.data[element] scaled = ((d - fprange[:, 0]) / (fprange[:, 1] - fprange[:, 0]) * 2.0 - 1.0) ax2.violinplot(scaled) ax2.set_ylabel('scaled value') ax2.set_xlim([0, self.data[element].shape[1] + 1]) ax2.set_ylim([-1.05, 1.05]) ax2.set_xlabel('fingerprint') else: ax.set_xlabel('fingerprint') fig.text(0.5, 0.25, '(No fprange in model; therefore no scaled ' 'fingerprints shown.)', ha='center') fig.text(0.5, 0.95, element, ha='center') if overlay: # Find all atoms. images = [atom.atoms for atom in overlay] images = hash_images(images) self._calc.descriptor.calculate_fingerprints(images) for atom in overlay: key = get_hash(atom.atoms) fingerprints = self._calc.descriptor.fingerprints[key] fingerprint = fingerprints[atom.index] fig = self.figures[fingerprint[0]] ax = fig.axes[0] ax.plot(range(1, len(fingerprint[1]) + 1), fingerprint[1], '.b') fprange = self._calc.model.parameters.fprange[atom.symbol] fprange = np.array(fprange) fprange.transpose() scaled = ((np.array(fingerprint[1]) - fprange[:, 0]) / (fprange[:, 1] - fprange[:, 0]) * 2.0 - 1.0) ax = fig.axes[1] ax.plot(range(1, len(fingerprint[1]) + 1), scaled, '.b') with PdfPages(name) as pdf: for fig in self.figures.values(): pdf.savefig(fig) pyplot.close(fig) def compile_fingerprints(self, images): """Calculates or looks up fingerprints and compiles them, per element, for the images. """ data = self.data = {} images = hash_images(images) self._calc.descriptor.calculate_fingerprints(images) for hash in images.keys(): fingerprints = self._calc.descriptor.fingerprints[hash] for element, fingerprint in fingerprints: if element not in data: data[element] = [] data[element].append(fingerprint) print(element, len(fingerprint)) for element in data.keys(): data[element] = np.array(data[element]) amp-0.6/amp/descriptor/gaussian.f900000644000175000017500000005146313137634440017117 0ustar muammarmuammar!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine calculate_g2(neighbornumbers, neighborpositions, & g_number, g_eta, p_gamma, rc, cutofffn, ri, num_neighbors, ridge) use cutoffs implicit none integer, dimension(num_neighbors):: neighbornumbers integer, dimension(1):: g_number double precision, dimension(num_neighbors, 3):: & neighborpositions double precision, dimension(3):: ri integer:: num_neighbors double precision:: g_eta, rc ! gamma parameter for the polynomial cutoff double precision, optional:: p_gamma character(len=20):: cutofffn double precision:: ridge !f2py intent(in):: neighbornumbers, neighborpositions, g_number !f2py intent(in):: g_eta, rc, ri, p_gamma !f2py intent(hide):: num_neighbors !f2py intent(out):: ridge integer:: j, match, xyz double precision, dimension(3):: Rij_vector double precision:: Rij, term ridge = 0.0d0 do j = 1, num_neighbors match = compare(neighbornumbers(j), g_number(1)) if (match == 1) then do xyz = 1, 3 Rij_vector(xyz) = & neighborpositions(j, xyz) - ri(xyz) end do Rij = sqrt(dot_product(Rij_vector, Rij_vector)) term = exp(-g_eta*(Rij**2.0d0) / (rc ** 2.0d0)) if (present(p_gamma)) then term = term * cutoff_fxn(Rij, rc, & cutofffn, p_gamma) else term = term * cutoff_fxn(Rij, rc, cutofffn) endif ridge = ridge + term end if end do CONTAINS function compare(try, val) result(match) ! Returns 1 if try is the same set as val, 0 if not. implicit none integer, intent(in):: try, val integer:: match if (try == val) then match = 1 else match = 0 end if end function compare end subroutine calculate_g2 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine calculate_g4(neighbornumbers, neighborpositions, & g_numbers, g_gamma, g_zeta, g_eta, rc, cutofffn, ri, & num_neighbors, ridge, p_gamma) use cutoffs implicit none integer, dimension(num_neighbors):: neighbornumbers integer, dimension(2):: g_numbers double precision, dimension(num_neighbors, 3):: & neighborpositions double precision, dimension(3):: ri integer:: num_neighbors double precision:: g_gamma, g_zeta, g_eta, rc ! gamma parameter for the polynomial cutoff double precision, optional:: p_gamma character(len=20):: cutofffn double precision:: ridge !f2py intent(in):: neighbornumbers, neighborpositions !f2py intent(in):: g_numbers, g_gamma, g_zeta !f2py intent(in):: g_eta, rc, ri, p_gamma !f2py intent(hide):: num_neighbors !f2py intent(out):: ridge integer:: j, k, match, xyz double precision, dimension(3):: Rij_vector, Rik_vector double precision, dimension(3):: Rjk_vector double precision:: Rij, Rik, Rjk, costheta, term ridge = 0.0d0 do j = 1, num_neighbors do k = (j + 1), num_neighbors match = compare(neighbornumbers(j), & neighbornumbers(k), g_numbers(1), g_numbers(2)) if (match == 1) then do xyz = 1, 3 Rij_vector(xyz) = & neighborpositions(j, xyz) - ri(xyz) Rik_vector(xyz) = & neighborpositions(k, xyz) - ri(xyz) Rjk_vector(xyz) = & neighborpositions(k, xyz) - & neighborpositions(j, xyz) end do Rij = sqrt(dot_product(Rij_vector, Rij_vector)) Rik = sqrt(dot_product(Rik_vector, Rik_vector)) Rjk = sqrt(dot_product(Rjk_vector, Rjk_vector)) costheta = & dot_product(Rij_vector, Rik_vector) / Rij / Rik term = (1.0d0 + g_gamma * costheta)**g_zeta term = term*& exp(-g_eta*(Rij**2 + Rik**2 + Rjk**2)& /(rc ** 2.0d0)) if (present(p_gamma)) then term = term*cutoff_fxn(Rij, rc, cutofffn, & p_gamma) term = term*cutoff_fxn(Rik, rc, cutofffn, & p_gamma) term = term*cutoff_fxn(Rjk, rc, cutofffn, & p_gamma) else term = term*cutoff_fxn(Rij, rc, cutofffn) term = term*cutoff_fxn(Rik, rc, cutofffn) term = term*cutoff_fxn(Rjk, rc, cutofffn) endif ridge = ridge + term end if end do end do ridge = ridge * 2.0d0**(1.0d0 - g_zeta) CONTAINS function compare(try1, try2, val1, val2) result(match) ! Returns 1 if (try1, try2) is the same set as (val1, val2), 0 if not. implicit none integer, intent(in):: try1, try2, val1, val2 integer:: match integer:: ntry1, ntry2, nval1, nval2 ! First sort to avoid endless logical loops. if (try1 < try2) then ntry1 = try1 ntry2 = try2 else ntry1 = try2 ntry2 = try1 end if if (val1 < val2) then nval1 = val1 nval2 = val2 else nval1 = val2 nval2 = val1 end if if (ntry1 == nval1 .AND. ntry2 == nval2) then match = 1 else match = 0 end if end function compare end subroutine calculate_g4 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine calculate_g2_prime(neighborindices, neighbornumbers, & neighborpositions, g_number, g_eta, rc, cutofffn, i, ri, m, l, & num_neighbors, ridge, p_gamma) use cutoffs implicit none integer, dimension(num_neighbors):: neighborindices integer, dimension(num_neighbors):: neighbornumbers integer, dimension(1):: g_number double precision, dimension(num_neighbors, 3):: & neighborpositions double precision, dimension(3):: ri, Rj integer:: num_neighbors, m, l, i double precision:: g_eta, rc ! gamma parameter for the polynomial cutoff double precision, optional:: p_gamma character(len=20):: cutofffn double precision:: ridge !f2py intent(in):: neighborindices, neighbornumbers !f2py intent(in):: neighborpositions, g_number !f2py intent(in):: g_eta, rc, i, ri, m, l, p_gamma !f2py intent(hide):: num_neighbors !f2py intent(out):: ridge integer:: j, match, xyz double precision, dimension(3):: Rij_vector double precision:: Rij, term1, dRijdRml ridge = 0.0d0 do j = 1, num_neighbors match = compare(neighbornumbers(j), g_number(1)) if (match == 1) then do xyz = 1, 3 Rj(xyz) = neighborpositions(j, xyz) Rij_vector(xyz) = Rj(xyz) - ri(xyz) end do dRijdRml = & dRij_dRml(i, neighborindices(j), ri, Rj, m, l) if (dRijdRml /= 0.0d0) then Rij = sqrt(dot_product(Rij_vector, Rij_vector)) if (present(p_gamma)) then term1 = - 2.0d0 * g_eta * Rij * & cutoff_fxn(Rij, rc, cutofffn, p_gamma) / & (rc ** 2.0d0) + cutoff_fxn_prime(Rij, rc, & cutofffn, p_gamma) else term1 = - 2.0d0 * g_eta * Rij * & cutoff_fxn(Rij, rc, cutofffn) / & (rc ** 2.0d0) + cutoff_fxn_prime(Rij, rc, & cutofffn) endif ridge = ridge + exp(- g_eta * (Rij**2.0d0) / & (rc ** 2.0d0)) * term1 * dRijdRml end if end if end do CONTAINS function compare(try, val) result(match) ! Returns 1 if try is the same set as val, 0 if not. implicit none integer, intent(in):: try, val integer:: match if (try == val) then match = 1 else match = 0 end if end function compare function dRij_dRml(i, j, Ri, Rj, m, l) integer i, j, m, l double precision, dimension(3):: Ri, Rj, Rij_vector double precision:: dRij_dRml, Rij do xyz = 1, 3 Rij_vector(xyz) = Rj(xyz) - Ri(xyz) end do Rij = sqrt(dot_product(Rij_vector, Rij_vector)) if ((m == i) .AND. (i /= j)) then dRij_dRml = - (Rj(l + 1) - Ri(l + 1)) / Rij else if ((m == j) .AND. (i /= j)) then dRij_dRml = (Rj(l + 1) - Ri(l + 1)) / Rij else dRij_dRml = 0.0d0 end if end function end subroutine calculate_g2_prime !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine calculate_g4_prime(neighborindices, neighbornumbers, & neighborpositions, g_numbers, g_gamma, g_zeta, g_eta, rc, & cutofffn, i, ri, m, l, num_neighbors, ridge, p_gamma) use cutoffs implicit none integer, dimension(num_neighbors):: neighborindices integer, dimension(num_neighbors):: neighbornumbers integer, dimension(2):: g_numbers double precision, dimension(num_neighbors, 3):: & neighborpositions double precision, dimension(3):: ri, Rj, Rk integer:: num_neighbors, i, m, l double precision:: g_gamma, g_zeta, g_eta, rc ! gamma parameter for the polynomial cutoff double precision, optional:: p_gamma character(len=20):: cutofffn double precision:: ridge !f2py intent(in):: neighbornumbers, neighborpositions !f2py intent(in):: g_numbers, g_gamma, g_zeta, p_gamma !f2py intent(in):: g_eta, rc, ri, neighborindices , i, m, l !f2py intent(hide):: num_neighbors !f2py intent(out):: ridge integer:: j, k, match, xyz double precision, dimension(3):: Rij_vector, Rik_vector double precision, dimension(3):: Rjk_vector double precision:: Rij, Rik, Rjk, costheta double precision:: c1, fcRij, fcRik, fcRjk double precision:: fcRijfcRikfcRjk, dCosthetadRml double precision:: dRijdRml, dRikdRml, dRjkdRml double precision:: term1, term2, term3, term4, term5 double precision:: term6 ridge = 0.0d0 do j = 1, num_neighbors do k = (j + 1), num_neighbors match = compare(neighbornumbers(j), & neighbornumbers(k), g_numbers(1), g_numbers(2)) if (match == 1) then do xyz = 1, 3 Rj(xyz) = neighborpositions(j, xyz) Rk(xyz) = neighborpositions(k, xyz) Rij_vector(xyz) = Rj(xyz) - ri(xyz) Rik_vector(xyz) = Rk(xyz) - ri(xyz) Rjk_vector(xyz) = Rk(xyz) - Rj(xyz) end do Rij = sqrt(dot_product(Rij_vector, Rij_vector)) Rik = sqrt(dot_product(Rik_vector, Rik_vector)) Rjk = sqrt(dot_product(Rjk_vector, Rjk_vector)) costheta = & dot_product(Rij_vector, Rik_vector) / Rij / Rik c1 = (1.0d0 + g_gamma * costheta) if (present(p_gamma)) then fcRij = cutoff_fxn(Rij, rc, cutofffn, p_gamma) fcRik = cutoff_fxn(Rik, rc, cutofffn, p_gamma) fcRjk = cutoff_fxn(Rjk, rc, cutofffn, p_gamma) else fcRij = cutoff_fxn(Rij, rc, cutofffn) fcRik = cutoff_fxn(Rik, rc, cutofffn) fcRjk = cutoff_fxn(Rjk, rc, cutofffn) endif if (g_zeta == 1.0d0) then term1 = exp(-g_eta*(Rij**2 + Rik**2 + Rjk**2)& / (rc ** 2.0d0)) else term1 = (c1**(g_zeta - 1.0d0)) & * exp(-g_eta*(Rij**2 + Rik**2 + Rjk**2)& / (rc ** 2.0d0)) end if term2 = 0.d0 fcRijfcRikfcRjk = fcRij * fcRik * fcRjk dCosthetadRml = & dCos_ijk_dR_ml(i, neighborindices(j), & neighborindices(k), ri, Rj, Rk, m, l) if (dCosthetadRml /= 0.d0) then term2 = term2 + g_gamma * g_zeta * dCosthetadRml end if dRijdRml = & dRij_dRml(i, neighborindices(j), ri, Rj, m, l) if (dRijdRml /= 0.0d0) then term2 = & term2 - 2.0d0 * c1 * g_eta * Rij * dRijdRml & / (rc ** 2.0d0) end if dRikdRml = & dRij_dRml(i, neighborindices(k), ri, Rk, m, l) if (dRikdRml /= 0.0d0) then term2 = & term2 - 2.0d0 * c1 * g_eta * Rik * dRikdRml & / (rc ** 2.0d0) end if dRjkdRml = & dRij_dRml(neighborindices(j), neighborindices(k), & Rj, Rk, m, l) if (dRjkdRml /= 0.0d0) then term2 = & term2 - 2.0d0 * c1 * g_eta * Rjk * dRjkdRml & / (rc ** 2.0d0) end if term3 = fcRijfcRikfcRjk * term2 if (present(p_gamma)) then term4 = & cutoff_fxn_prime(Rij, rc, cutofffn, p_gamma) & * dRijdRml * fcRik * fcRjk term5 = & fcRij * cutoff_fxn_prime(Rik, rc, cutofffn, & p_gamma) * dRikdRml * fcRjk term6 = & fcRij * fcRik * cutoff_fxn_prime(Rjk, rc, & cutofffn, p_gamma) * dRjkdRml else term4 = & cutoff_fxn_prime(Rij, rc, cutofffn) & * dRijdRml * fcRik * fcRjk term5 = & fcRij * cutoff_fxn_prime(Rik, rc, cutofffn) & * dRikdRml * fcRjk term6 = & fcRij * fcRik * cutoff_fxn_prime(Rjk, rc, & cutofffn) * dRjkdRml endif ridge = ridge + & term1 * (term3 + c1 * (term4 + term5 + term6)) end if end do end do ridge = ridge * (2.0d0**(1.0d0 - g_zeta)) CONTAINS function compare(try1, try2, val1, val2) result(match) ! Returns 1 if (try1, try2) is the same set as (val1, val2), 0 if not. implicit none integer, intent(in):: try1, try2, val1, val2 integer:: match integer:: ntry1, ntry2, nval1, nval2 ! First sort to avoid endless logical loops. if (try1 < try2) then ntry1 = try1 ntry2 = try2 else ntry1 = try2 ntry2 = try1 end if if (val1 < val2) then nval1 = val1 nval2 = val2 else nval1 = val2 nval2 = val1 end if if (ntry1 == nval1 .AND. ntry2 == nval2) then match = 1 else match = 0 end if end function compare function dRij_dRml(i, j, Ri, Rj, m, l) integer i, j, m, l double precision, dimension(3):: Ri, Rj, Rij_vector double precision:: dRij_dRml, Rij do xyz = 1, 3 Rij_vector(xyz) = Rj(xyz) - Ri(xyz) end do Rij = sqrt(dot_product(Rij_vector, Rij_vector)) if ((m == i) .AND. (i /= j)) then dRij_dRml = - (Rj(l + 1) - Ri(l + 1)) / Rij else if ((m == j) .AND. (i /= j)) then dRij_dRml = (Rj(l + 1) - Ri(l + 1)) / Rij else dRij_dRml = 0.0d0 end if end function function dCos_ijk_dR_ml(i, j, k, ri, Rj, Rk, m, l) implicit none integer:: i, j, k, m, l double precision:: dCos_ijk_dR_ml double precision, dimension(3):: ri, Rj, Rk integer, dimension(3):: dRijdRml, dRikdRml double precision:: dRijdRml_, dRikdRml_ do xyz = 1, 3 Rij_vector(xyz) = Rj(xyz) - ri(xyz) Rik_vector(xyz) = Rk(xyz) - ri(xyz) end do Rij = sqrt(dot_product(Rij_vector, Rij_vector)) Rik = sqrt(dot_product(Rik_vector, Rik_vector)) dCos_ijk_dR_ml = 0.0d0 dRijdRml = dRij_dRml_vector(i, j, m, l) if ((dRijdRml(1) /= 0) .OR. (dRijdRml(2) /= 0) .OR. & (dRijdRml(3) /= 0)) then dCos_ijk_dR_ml = dCos_ijk_dR_ml + 1.0d0 / (Rij * Rik) * & dot_product(dRijdRml, Rik_vector) end if dRikdRml = dRij_dRml_vector(i, k, m, l) if ((dRikdRml(1) /= 0) .OR. (dRikdRml(2) /= 0) .OR. & (dRikdRml(3) /= 0)) then dCos_ijk_dR_ml = dCos_ijk_dR_ml + 1.0d0 / (Rij * Rik) * & dot_product(dRikdRml, Rij_vector) end if dRijdRml_ = dRij_dRml(i, j, ri, Rj, m, l) if (dRijdRml_ /= 0.0d0) then dCos_ijk_dR_ml = dCos_ijk_dR_ml - 1.0d0 / (Rij * Rij * Rik) * & dot_product(Rij_vector, Rik_vector) * dRijdRml_ end if dRikdRml_ = dRij_dRml(i, k, ri, Rk, m, l) if (dRikdRml_ /= 0.0d0) then dCos_ijk_dR_ml = dCos_ijk_dR_ml - 1.0d0 / (Rij * Rik * Rik) * & dot_product(Rij_vector, Rik_vector) * dRikdRml_ end if end function function dRij_dRml_vector(i, j, m, l) implicit none integer:: i, j, m, l, c1 integer, dimension(3):: dRij_dRml_vector if ((m /= i) .AND. (m /= j)) then dRij_dRml_vector(1) = 0 dRij_dRml_vector(2) = 0 dRij_dRml_vector(3) = 0 else c1 = Kronecker(m, j) - Kronecker(m, i) dRij_dRml_vector(1) = c1 * Kronecker(0, l) dRij_dRml_vector(2) = c1 * Kronecker(1, l) dRij_dRml_vector(3) = c1 * Kronecker(2, l) end if end function function Kronecker(i, j) implicit none integer:: i, j integer:: Kronecker if (i == j) then Kronecker = 1 else Kronecker = 0 end if end function end subroutine calculate_g4_prime !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! amp-0.6/amp/descriptor/example.py0000644000175000017500000003123413137634440016764 0ustar muammarmuammarimport time import numpy as np from ase.calculators.calculator import Parameters from ..utilities import Data, Logger, importer from .cutoffs import Cosine NeighborList = importer('NeighborList') class AtomCenteredExample(object): """Class that calculates fingerprints. This is an example class that doesn't do much; it just shows the code structure. If making your own module, you can copy and modify this one. Parameters ---------- cutoff : object or float Cutoff function. Can be also fed as a float representing the radius above which neighbor interactions are ignored. Default is 6.5 Angstroms. anotherparameter : float Just an example. dblabel : str Optional separate prefix/location for database files, including fingerprints, fingerprint derivatives, and neighborlists. This file location can be shared between calculator instances to avoid re-calculating redundant information. If not supplied, just uses the value from label. elements : list List of allowed elements present in the system. If not provided, will be found automatically. version : str Version of fingerprints. Raises ------ RuntimeError, TypeError """ def __init__(self, cutoff=Cosine(6.5), anotherparameter=12.2, dblabel=None, elements=None, version=None, mode='atom-centered'): # Check of the version of descriptor, particularly if restarting. compatibleversions = ['2016.02', ] if (version is not None) and version not in compatibleversions: raise RuntimeError('Error: Trying to use Example fingerprints' ' version %s, but this module only supports' ' versions %s. You may need an older or ' ' newer version of Amp.' % (version, compatibleversions)) else: version = compatibleversions[-1] # Check that the mode is atom-centered. if mode != 'atom-centered': raise RuntimeError('This scheme only works ' 'in atom-centered mode. %s ' 'specified.' % mode) # If the cutoff is provided as a number, Cosine function will be used # by default. if isinstance(cutoff, int) or isinstance(cutoff, float): cutoff = Cosine(cutoff) # The parameters dictionary contains the minimum information # to produce a compatible descriptor; that is, one that gives # an identical fingerprint when fed an ASE image. p = self.parameters = Parameters( {'importname': '.descriptor.example.AtomCenteredExample', 'mode': 'atom-centered'}) p.version = version p.cutoff = cutoff.Rc p.cutofffn = cutoff.__class__.__name__ p.anotherparameter = anotherparameter p.elements = elements self.dblabel = dblabel self.parent = None # Can hold a reference to main Amp instance. def tostring(self): """Returns an evaluatable representation of the calculator that can be used to restart the calculator.""" return self.parameters.tostring() def calculate_fingerprints(self, images, parallel=None, log=None, calculate_derivatives=False): """Calculates the fingerpints of the images, for the ones not already done. Parameters ---------- images : list or str List of ASE atoms objects with positions, symbols, energies, and forces in ASE format. This is the training set of data. This can also be the path to an ASE trajectory (.traj) or database (.db) file. Energies can be obtained from any reference, e.g. DFT calculations. parallel : dict Configuration for parallelization. Should be in same form as in amp.Amp. log : Logger object Write function at which to log data. Note this must be a callable function. calculate_derivatives : bool Decides whether or not fingerprintprimes should also be calculated. """ if parallel is None: parallel = {'cores': 1} log = Logger(file=None) if log is None else log if (self.dblabel is None) and hasattr(self.parent, 'dblabel'): self.dblabel = self.parent.dblabel self.dblabel = 'amp-data' if self.dblabel is None else self.dblabel p = self.parameters log('Cutoff radius: %.2f' % p.cutoff) log('Cutoff function: %s' % p.cutofffn) if p.elements is None: log('Finding unique set of elements in training data.') p.elements = set([atom.symbol for atoms in images.values() for atom in atoms]) p.elements = sorted(p.elements) log('%i unique elements included: ' % len(p.elements) + ', '.join(p.elements)) log('anotherparameter: %.3f' % p.anotherparameter) log('Calculating neighborlists...', tic='nl') if not hasattr(self, 'neighborlist'): calc = NeighborlistCalculator(cutoff=p.cutoff) self.neighborlist = Data(filename='%s-neighborlists' % self.dblabel, calculator=calc) self.neighborlist.calculate_items(images, parallel=parallel, log=log) log('...neighborlists calculated.', toc='nl') log('Fingerprinting images...', tic='fp') if not hasattr(self, 'fingerprints'): calc = FingerprintCalculator(neighborlist=self.neighborlist, anotherparamter=p.anotherparameter, cutoff=p.cutoff, cutofffn=p.cutofffn) self.fingerprints = Data(filename='%s-fingerprints' % self.dblabel, calculator=calc) self.fingerprints.calculate_items(images, parallel=parallel, log=log) log('...fingerprints calculated.', toc='fp') # Calculators ################################################################# # Neighborlist Calculator class NeighborlistCalculator: """For integration with .utilities.Data For each image fed to calculate, a list of neighbors with offset distances is returned. Parameters ---------- cutoff : float Radius above which neighbor interactions are ignored. """ def __init__(self, cutoff): self.globals = Parameters({'cutoff': cutoff}) self.keyed = Parameters() self.parallel_command = 'calculate_neighborlists' def calculate(self, image, key): """For integration with .utilities.Data For each image fed to calculate, a list of neighbors with offset distances is returned. Parameters ---------- image : object ASE atoms object. key : str key of the image after being hashed. """ cutoff = self.globals.cutoff n = NeighborList(cutoffs=[cutoff / 2.] * len(image), self_interaction=False, bothways=True, skin=0.) n.update(image) return [n.get_neighbors(index) for index in range(len(image))] class FingerprintCalculator: """For integration with .utilities.Data""" def __init__(self, neighborlist, anotherparamter, cutoff, cutofffn): self.globals = Parameters({'cutoff': cutoff, 'cutofffn': cutofffn, 'anotherparameter': anotherparamter}) self.keyed = Parameters({'neighborlist': neighborlist}) self.parallel_command = 'calculate_fingerprints' def calculate(self, image, key): """Makes a list of fingerprints, one per atom, for the fed image. """ nl = self.keyed.neighborlist[key] fingerprints = [] for atom in image: symbol = atom.symbol index = atom.index neighbors, offsets = nl[index] neighborsymbols = [image[_].symbol for _ in neighbors] Rs = [image.positions[neighbor] + np.dot(offset, image.cell) for (neighbor, offset) in zip(neighbors, offsets)] self.atoms = image indexfp = self.get_fingerprint(index, symbol, neighborsymbols, Rs) fingerprints.append(indexfp) return fingerprints def get_fingerprint(self, index, symbol, n_symbols, Rs): """ Returns the fingerprint of symmetry function values for atom specified by its index and symbol. n_symbols and Rs are lists of neighbors' symbols and Cartesian positions, respectively. This function doesn't actually do anything but sleep and return a vector of ones. Parameters ---------- index : int index: Index of the center atom. symbol: str Symbol of the center atom. n_symbols: list of str List of neighbors' symbols. Rs: list of list of float List of Cartesian atomic positions. Returns ------- symbols, fingerprints : list of float Fingerprints for atom specified by its index and symbol. """ time.sleep(1.0) # Pretend to do some work. fingerprint = [1., 1., 1., 1.] return symbol, fingerprint if __name__ == "__main__": """Directly calling this module; apparently from another node. Calls should come as python -m amp.descriptor.example id hostname:port This session will then start a zmq session with that socket, labeling itself with id. Instructions on what to do will come from the socket. """ import sys import tempfile import zmq from ..utilities import MessageDictionary hostsocket = sys.argv[-1] proc_id = sys.argv[-2] msg = MessageDictionary(proc_id) # Send standard lines to stdout signaling process started and where # error is directed. This should be caught by pxssh. (This could # alternatively be done by zmq, but this works.) print('') # Signal that program started. sys.stderr = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.stderr') print('Log and error written to %s' % sys.stderr.name) # Establish client session via zmq; find purpose. context = zmq.Context() socket = context.socket(zmq.REQ) socket.connect('tcp://%s' % hostsocket) socket.send_pyobj(msg('')) purpose = socket.recv_pyobj() if purpose == 'calculate_neighborlists': # Request variables. socket.send_pyobj(msg('', 'cutoff')) cutoff = socket.recv_pyobj() socket.send_pyobj(msg('', 'images')) images = socket.recv_pyobj() # sys.stderr.write(str(images)) # Just to see if they are there. # Perform the calculations. calc = NeighborlistCalculator(cutoff=cutoff) neighborlist = {} # for key in images.iterkeys(): while len(images) > 0: key, image = images.popitem() # Reduce memory. neighborlist[key] = calc.calculate(image, key) # Send the results. socket.send_pyobj(msg('', neighborlist)) socket.recv_string() # Needed to complete REQ/REP. elif purpose == 'calculate_fingerprints': # Request variables. socket.send_pyobj(msg('', 'cutoff')) cutoff = socket.recv_pyobj() socket.send_pyobj(msg('', 'cutofffn')) cutofffn = socket.recv_pyobj() socket.send_pyobj(msg('', 'anotherparameter')) anotherparameter = socket.recv_pyobj() socket.send_pyobj(msg('', 'neighborlist')) neighborlist = socket.recv_pyobj() socket.send_pyobj(msg('', 'images')) images = socket.recv_pyobj() calc = FingerprintCalculator(neighborlist, anotherparameter, cutoff, cutofffn) result = {} while len(images) > 0: key, image = images.popitem() # Reduce memory. result[key] = calc.calculate(image, key) if len(images) % 100 == 0: socket.send_pyobj(msg('', len(images))) socket.recv_string() # Needed to complete REQ/REP. # Send the results. socket.send_pyobj(msg('', result)) socket.recv_string() # Needed to complete REQ/REP. else: raise NotImplementedError('purpose %s unknown.' % purpose) amp-0.6/amp/descriptor/gaussian.py0000644000175000017500000014062613137634440017151 0ustar muammarmuammarimport numpy as np from ase.data import atomic_numbers from ase.calculators.calculator import Parameters from ..utilities import Data, Logger, importer from .cutoffs import Cosine, dict2cutoff NeighborList = importer('NeighborList') try: from .. import fmodules except ImportError: fmodules = None class Gaussian(object): """Class that calculates Gaussian fingerprints (i.e., Behler-style). Parameters ---------- cutoff : object or float Cutoff function, typically from amp.descriptor.cutoffs. Can be also fed as a float representing the radius above which neighbor interactions are ignored; in this case a cosine cutoff function will be employed. Default is a 6.5-Angstrom cosine cutoff. Gs : dict Dictionary of symbols and lists of dictionaries for making symmetry functions. Either auto-genetrated, or given in the following form, for example: >>> Gs = {"O": [{"type":"G2", "element":"O", "eta":10.}, ... {"type":"G4", "elements":["O", "Au"], ... "eta":5., "gamma":1., "zeta":1.0}], ... "Au": [{"type":"G2", "element":"O", "eta":2.}, ... {"type":"G4", "elements":["O", "Au"], ... "eta":2., "gamma":1., "zeta":5.0}]} dblabel : str Optional separate prefix/location for database files, including fingerprints, fingerprint derivatives, and neighborlists. This file location can be shared between calculator instances to avoid re-calculating redundant information. If not supplied, just uses the value from label. elements : list List of allowed elements present in the system. If not provided, will be found automatically. version : str Version of fingerprints. fortran : bool If True, will use fortran modules, if False, will not. mode : str Can be either 'atom-centered' or 'image-centered'. Raises ------ RuntimeError """ def __init__(self, cutoff=Cosine(6.5), Gs=None, dblabel=None, elements=None, version=None, fortran=True, mode='atom-centered'): # Check of the version of descriptor, particularly if restarting. compatibleversions = ['2015.12', ] if (version is not None) and version not in compatibleversions: raise RuntimeError('Error: Trying to use Gaussian fingerprints' ' version %s, but this module only supports' ' versions %s. You may need an older or ' ' newer version of Amp.' % (version, compatibleversions)) else: version = compatibleversions[-1] # Check that the mode is atom-centered. if mode != 'atom-centered': raise RuntimeError('Gaussian scheme only works ' 'in atom-centered mode. %s ' 'specified.' % mode) # If the cutoff is provided as a number, Cosine function will be used # by default. if isinstance(cutoff, int) or isinstance(cutoff, float): cutoff = Cosine(cutoff) # If the cutoff is provided as a dictionary, assume we need to load it # with dict2cutoff. if type(cutoff) is dict: cutoff = dict2cutoff(cutoff) # The parameters dictionary contains the minimum information # to produce a compatible descriptor; that is, one that gives # an identical fingerprint when fed an ASE image. p = self.parameters = Parameters( {'importname': '.descriptor.gaussian.Gaussian', 'mode': 'atom-centered'}) p.version = version p.cutoff = cutoff.todict() p.Gs = Gs p.elements = elements self.dblabel = dblabel self.fortran = fortran self.parent = None # Can hold a reference to main Amp instance. def tostring(self): """Returns an evaluatable representation of the calculator that can be used to restart the calculator. """ return self.parameters.tostring() def calculate_fingerprints(self, images, parallel=None, log=None, calculate_derivatives=False): """Calculates the fingerpints of the images, for the ones not already done. Parameters ---------- images : dict Dictionary of images; the key is a unique ID assigned to each image and each value is an ASE atoms object. Typically created from amp.utilities.hash_images. parallel : dict Configuration for parallelization. Should be in same form as in amp.Amp. log : Logger object Write function at which to log data. Note this must be a callable function. calculate_derivatives : bool Decides whether or not fingerprintprimes should also be calculated. """ if parallel is None: parallel = {'cores': 1} log = Logger(file=None) if log is None else log if (self.dblabel is None) and hasattr(self.parent, 'dblabel'): self.dblabel = self.parent.dblabel self.dblabel = 'amp-data' if self.dblabel is None else self.dblabel p = self.parameters log('Cutoff function: %s' % repr(dict2cutoff(p.cutoff))) if p.elements is None: log('Finding unique set of elements in training data.') p.elements = set([atom.symbol for atoms in images.values() for atom in atoms]) p.elements = sorted(p.elements) log('%i unique elements included: ' % len(p.elements) + ', '.join(p.elements)) if p.Gs is None: log('No symmetry functions supplied; creating defaults.') p.Gs = make_default_symmetry_functions(p.elements) log('Number of symmetry functions for each element:') for _ in p.Gs.keys(): log(' %2s: %i' % (_, len(p.Gs[_]))) for element, fingerprints in p.Gs.items(): log('{} feature vector functions:'.format(element)) for index, fp in enumerate(fingerprints): if fp['type'] == 'G2': log(' {}: {}, {}, eta = {}' .format(index, fp['type'], fp['element'], fp['eta'])) elif fp['type'] == 'G4': log(' {}: {}, ({}, {}), eta={}, gamma={}, zeta={}' .format(index, fp['type'], fp['elements'][0], fp['elements'][1], fp['eta'], fp['gamma'], fp['zeta'])) else: log(str(fp)) log('Calculating neighborlists...', tic='nl') if not hasattr(self, 'neighborlist'): calc = NeighborlistCalculator(cutoff=p.cutoff['kwargs']['Rc']) self.neighborlist = \ Data(filename='%s-neighborlists' % self.dblabel, calculator=calc) self.neighborlist.calculate_items(images, parallel=parallel, log=log) log('...neighborlists calculated.', toc='nl') log('Fingerprinting images...', tic='fp') if not hasattr(self, 'fingerprints'): calc = FingerprintCalculator(neighborlist=self.neighborlist, Gs=p.Gs, cutoff=p.cutoff, fortran=self.fortran) self.fingerprints = Data(filename='%s-fingerprints' % self.dblabel, calculator=calc) self.fingerprints.calculate_items(images, parallel=parallel, log=log) log('...fingerprints calculated.', toc='fp') if calculate_derivatives: log('Calculating fingerprint derivatives...', tic='derfp') if not hasattr(self, 'fingerprintprimes'): calc = \ FingerprintPrimeCalculator(neighborlist=self.neighborlist, Gs=p.Gs, cutoff=p.cutoff, fortran=self.fortran) self.fingerprintprimes = \ Data(filename='%s-fingerprint-primes' % self.dblabel, calculator=calc) self.fingerprintprimes.calculate_items( images, parallel=parallel, log=log) log('...fingerprint derivatives calculated.', toc='derfp') # Calculators ################################################################# # Neighborlist Calculator class NeighborlistCalculator: """For integration with .utilities.Data For each image fed to calculate, a list of neighbors with offset distances is returned. Parameters ---------- cutoff : float Radius above which neighbor interactions are ignored. """ def __init__(self, cutoff): self.globals = Parameters({'cutoff': cutoff}) self.keyed = Parameters() self.parallel_command = 'calculate_neighborlists' def calculate(self, image, key): """For integration with .utilities.Data For each image fed to calculate, a list of neighbors with offset distances is returned. Parameters ---------- image : object ASE atoms object. key : str key of the image after being hashed. """ cutoff = self.globals.cutoff n = NeighborList(cutoffs=[cutoff / 2.] * len(image), self_interaction=False, bothways=True, skin=0.) n.update(image) return [n.get_neighbors(index) for index in range(len(image))] class FingerprintCalculator: """For integration with .utilities.Data Parameters ---------- neighborlist : list of str List of neighbors. Gs : dict Dictionary of symbols and lists of dictionaries for making symmetry functions. Either auto-genetrated, or given in the following form, for example: >>> Gs = {"O": [{"type":"G2", "element":"O", "eta":10.}, ... {"type":"G4", "elements":["O", "Au"], ... "eta":5., "gamma":1., "zeta":1.0}], ... "Au": [{"type":"G2", "element":"O", "eta":2.}, ... {"type":"G4", "elements":["O", "Au"], ... "eta":2., "gamma":1., "zeta":5.0}]} cutoff : float Radius above which neighbor interactions are ignored. fortran : bool If True, will use fortran modules, if False, will not. """ def __init__(self, neighborlist, Gs, cutoff, fortran): self.globals = Parameters({'cutoff': cutoff, 'Gs': Gs}) self.keyed = Parameters({'neighborlist': neighborlist}) self.parallel_command = 'calculate_fingerprints' self.fortran = fortran def calculate(self, image, key): """Makes a list of fingerprints, one per atom, for the fed image. Parameters ---------- image : object ASE atoms object. key : str key of the image after being hashed. """ self.atoms = image nl = self.keyed.neighborlist[key] fingerprints = [] for atom in image: symbol = atom.symbol index = atom.index neighborindices, neighboroffsets = nl[index] neighborsymbols = [image[_].symbol for _ in neighborindices] neighborpositions = \ [image.positions[neighbor] + np.dot(offset, image.cell) for (neighbor, offset) in zip(neighborindices, neighboroffsets)] indexfp = self.get_fingerprint( index, symbol, neighborsymbols, neighborpositions) fingerprints.append(indexfp) return fingerprints def get_fingerprint(self, index, symbol, neighborsymbols, neighborpositions): """Returns the fingerprint of symmetry function values for atom specified by its index and symbol. neighborsymbols and neighborpositions are lists of neighbors' symbols and Cartesian positions, respectively. Parameters ---------- index : int Index of the center atom. symbol : str Symbol of the center atom. neighborsymbols : list of str List of neighbors' symbols. neighborpositions : list of list of float List of Cartesian atomic positions. Returns ------- symbol, fingerprint : list of float fingerprints for atom specified by its index and symbol. """ Ri = self.atoms[index].position num_symmetries = len(self.globals.Gs[symbol]) fingerprint = [None] * num_symmetries for count in range(num_symmetries): G = self.globals.Gs[symbol][count] if G['type'] == 'G2': ridge = calculate_G2(neighborsymbols, neighborpositions, G['element'], G['eta'], self.globals.cutoff, Ri, self.fortran) elif G['type'] == 'G4': ridge = calculate_G4(neighborsymbols, neighborpositions, G['elements'], G['gamma'], G['zeta'], G['eta'], self.globals.cutoff, Ri, self.fortran) else: raise NotImplementedError('Unknown G type: %s' % G['type']) fingerprint[count] = ridge return symbol, fingerprint class FingerprintPrimeCalculator: """For integration with .utilities.Data Parameters ---------- neighborlist : list of str List of neighbors. Gs : dict Dictionary of symbols and lists of dictionaries for making symmetry functions. Either auto-genetrated, or given in the following form, for example: >>> Gs = {"O": [{"type":"G2", "element":"O", "eta":10.}, ... {"type":"G4", "elements":["O", "Au"], ... "eta":5., "gamma":1., "zeta":1.0}], ... "Au": [{"type":"G2", "element":"O", "eta":2.}, ... {"type":"G4", "elements":["O", "Au"], ... "eta":2., "gamma":1., "zeta":5.0}]} cutoff : float Radius above which neighbor interactions are ignored. fortran : bool If True, will use fortran modules, if False, will not. """ def __init__(self, neighborlist, Gs, cutoff, fortran): self.globals = Parameters({'cutoff': cutoff, 'Gs': Gs}) self.keyed = Parameters({'neighborlist': neighborlist}) self.parallel_command = 'calculate_fingerprint_primes' self.fortran = fortran def calculate(self, image, key): """Makes a list of fingerprint derivatives, one per atom, for the fed image. Parameters ---------- image : object ASE atoms object. key : str key of the image after being hashed. """ self.atoms = image nl = self.keyed.neighborlist[key] fingerprintprimes = {} for atom in image: selfsymbol = atom.symbol selfindex = atom.index selfneighborindices, selfneighboroffsets = nl[selfindex] selfneighborsymbols = [ image[_].symbol for _ in selfneighborindices] selfneighborpositions = [image.positions[_index] + np.dot(_offset, image.get_cell()) for _index, _offset in zip(selfneighborindices, selfneighboroffsets)] for i in range(3): # Calculating derivative of fingerprints of self atom w.r.t. # coordinates of itself. fpprime = self.get_fingerprintprime( selfindex, selfsymbol, selfneighborindices, selfneighborsymbols, selfneighborpositions, selfindex, i) fingerprintprimes[ (selfindex, selfsymbol, selfindex, selfsymbol, i)] = \ fpprime # Calculating derivative of fingerprints of neighbor atom # w.r.t. coordinates of self atom. for nindex, nsymbol, noffset in \ zip(selfneighborindices, selfneighborsymbols, selfneighboroffsets): # for calculating forces, summation runs over neighbor # atoms of type II (within the main cell only) if noffset.all() == 0: nneighborindices, nneighboroffsets = nl[nindex] nneighborsymbols = \ [image[_].symbol for _ in nneighborindices] neighborpositions = [image.positions[_index] + np.dot(_offset, image.get_cell()) for _index, _offset in zip(nneighborindices, nneighboroffsets)] # for calculating derivatives of fingerprints, # summation runs over neighboring atoms of type # I (either inside or outside the main cell) fpprime = self.get_fingerprintprime( nindex, nsymbol, nneighborindices, nneighborsymbols, neighborpositions, selfindex, i) fingerprintprimes[ (selfindex, selfsymbol, nindex, nsymbol, i)] = \ fpprime return fingerprintprimes def get_fingerprintprime(self, index, symbol, neighborindices, neighborsymbols, neighborpositions, m, l): """ Returns the value of the derivative of G for atom with index and symbol with respect to coordinate x_{l} of atom index m. neighborindices, neighborsymbols and neighborpositions are lists of neighbors' indices, symbols and Cartesian positions, respectively. Parameters ---------- index : int Index of the center atom. symbol : str Symbol of the center atom. neighborindices : list of int List of neighbors' indices. neighborsymbols : list of str List of neighbors' symbols. neighborpositions : list of list of float List of Cartesian atomic positions. m : int Index of the pair atom. l : int Direction of the derivative; is an integer from 0 to 2. Returns ------- fingerprintprime : list of float The value of the derivative of the fingerprints for atom with index and symbol with respect to coordinate x_{l} of atom index m. """ num_symmetries = len(self.globals.Gs[symbol]) Rindex = self.atoms.positions[index] fingerprintprime = [None] * num_symmetries for count in range(num_symmetries): G = self.globals.Gs[symbol][count] if G['type'] == 'G2': ridge = calculate_G2_prime( neighborindices, neighborsymbols, neighborpositions, G['element'], G['eta'], self.globals.cutoff, index, Rindex, m, l, self.fortran) elif G['type'] == 'G4': ridge = calculate_G4_prime( neighborindices, neighborsymbols, neighborpositions, G['elements'], G['gamma'], G['zeta'], G['eta'], self.globals.cutoff, index, Rindex, m, l, self.fortran) else: raise NotImplementedError('Unknown G type: %s' % G['type']) fingerprintprime[count] = ridge return fingerprintprime # Auxiliary functions ######################################################### def calculate_G2(neighborsymbols, neighborpositions, G_element, eta, cutoff, Ri, fortran): """Calculate G2 symmetry function. Ideally this will not be used but will be a template for how to build the fortran version (and serves as a slow backup if the fortran one goes uncompiled). See Eq. 13a of the supplementary information of Khorshidi, Peterson, CPC(2016). Parameters ---------- neighborsymbols : list of str List of symbols of all neighbor atoms. neighborpositions : list of list of floats List of Cartesian atomic positions. G_element : str Chemical symbol of the center atom. eta : float Parameter of Gaussian symmetry functions. cutoff : dict Cutoff function, typically from amp.descriptor.cutoffs. Should be also formatted as a dictionary by todict method, e.g. cutoff=Cosine(6.5).todict() Ri : list Position of the center atom. Should be fed as a list of three floats. fortran : bool If True, will use the fortran subroutines, else will not. Returns ------- ridge : float G2 fingerprint. """ if fortran: # fortran version; faster G_number = [atomic_numbers[G_element]] neighbornumbers = \ [atomic_numbers[symbol] for symbol in neighborsymbols] if len(neighbornumbers) == 0: ridge = 0. else: cutofffn = cutoff['name'] Rc = cutoff['kwargs']['Rc'] args_calculate_g2 = dict( neighbornumbers=neighbornumbers, neighborpositions=neighborpositions, g_number=G_number, g_eta=eta, rc=Rc, cutofffn=cutofffn, ri=Ri ) if cutofffn == 'Polynomial': args_calculate_g2['p_gamma'] = cutoff['kwargs']['gamma'] ridge = fmodules.calculate_g2(**args_calculate_g2) else: Rc = cutoff['kwargs']['Rc'] cutoff_fxn = dict2cutoff(cutoff) ridge = 0. # One aspect of a fingerprint :) num_neighbors = len(neighborpositions) # number of neighboring atoms for count in range(num_neighbors): symbol = neighborsymbols[count] Rj = neighborpositions[count] if symbol == G_element: Rij = np.linalg.norm(Rj - Ri) args_cutoff_fxn = dict(Rij=Rij) if cutoff['name'] == 'Polynomial': args_cutoff_fxn['gamma'] = cutoff['kwargs']['gamma'] ridge += (np.exp(-eta * (Rij ** 2.) / (Rc ** 2.)) * cutoff_fxn(**args_cutoff_fxn)) return ridge def calculate_G4(neighborsymbols, neighborpositions, G_elements, gamma, zeta, eta, cutoff, Ri, fortran): """Calculate G4 symmetry function. Ideally this will not be used but will be a template for how to build the fortran version (and serves as a slow backup if the fortran one goes uncompiled). See Eq. 13c of the supplementary information of Khorshidi, Peterson, CPC(2016). Parameters ---------- neighborsymbols : list of str List of symbols of neighboring atoms. neighborpositions : list of list of floats List of Cartesian atomic positions of neighboring atoms. G_elements : list of str A list of two members, each member is the chemical species of one of the neighboring atoms forming the triangle with the center atom. gamma : float Parameter of Gaussian symmetry functions. zeta : float Parameter of Gaussian symmetry functions. eta : float Parameter of Gaussian symmetry functions. cutoff : dict Cutoff function, typically from amp.descriptor.cutoffs. Should be also formatted as a dictionary by todict method, e.g. cutoff=Cosine(6.5).todict() Ri : list Position of the center atom. Should be fed as a list of three floats. fortran : bool If True, will use the fortran subroutines, else will not. Returns ------- ridge : float G4 fingerprint. """ if fortran: # fortran version; faster G_numbers = sorted([atomic_numbers[el] for el in G_elements]) neighbornumbers = \ [atomic_numbers[symbol] for symbol in neighborsymbols] if len(neighborpositions) == 0: return 0. else: cutofffn = cutoff['name'] Rc = cutoff['kwargs']['Rc'] args_calculate_g4 = dict( neighbornumbers=neighbornumbers, neighborpositions=neighborpositions, g_numbers=G_numbers, g_gamma=gamma, g_zeta=zeta, g_eta=eta, rc=Rc, cutofffn=cutofffn, ri=Ri ) if cutofffn == 'Polynomial': args_calculate_g4['p_gamma'] = cutoff['kwargs']['gamma'] ridge = fmodules.calculate_g4(**args_calculate_g4) return ridge else: Rc = cutoff['kwargs']['Rc'] cutoff_fxn = dict2cutoff(cutoff) ridge = 0. counts = range(len(neighborpositions)) for j in counts: for k in counts[(j + 1):]: els = sorted([neighborsymbols[j], neighborsymbols[k]]) if els != G_elements: continue Rij_vector = neighborpositions[j] - Ri Rij = np.linalg.norm(Rij_vector) Rik_vector = neighborpositions[k] - Ri Rik = np.linalg.norm(Rik_vector) Rjk_vector = neighborpositions[k] - neighborpositions[j] Rjk = np.linalg.norm(Rjk_vector) cos_theta_ijk = np.dot(Rij_vector, Rik_vector) / Rij / Rik term = (1. + gamma * cos_theta_ijk) ** zeta term *= np.exp(-eta * (Rij ** 2. + Rik ** 2. + Rjk ** 2.) / (Rc ** 2.)) _Rij = dict(Rij=Rij) _Rik = dict(Rij=Rik) _Rjk = dict(Rij=Rjk) if cutoff['name'] == 'Polynomial': _Rij['gamma'] = cutoff['kwargs']['gamma'] _Rik['gamma'] = cutoff['kwargs']['gamma'] _Rjk['gamma'] = cutoff['kwargs']['gamma'] term *= cutoff_fxn(**_Rij) term *= cutoff_fxn(**_Rik) term *= cutoff_fxn(**_Rjk) ridge += term ridge *= 2. ** (1. - zeta) return ridge def make_symmetry_functions(elements, type, etas, zetas=None, gammas=None): """Helper function to create Gaussian symmetry functions. Returns a list of dictionaries with symmetry function parameters in the format expected by the Gaussian class. Parameters ---------- elements : list of str List of element types. The first in the list is considered the central element for this fingerprint. #FIXME: Does that matter? type : str Either G2 or G4. etas : list of floats eta values to use in G2 or G4 fingerprints zetas : list of floats zeta values to use in G4 fingerprints gammas : list of floats gamma values to use in G4 fingerprints Returns ------- G : list of dicts A list, each item in the list contains a dictionary of fingerprint parameters. """ if type == 'G2': G = [{'type': 'G2', 'element': element, 'eta': eta} for eta in etas for element in elements] return G elif type == 'G4': G = [] for eta in etas: for zeta in zetas: for gamma in gammas: for i1, el1 in enumerate(elements): for el2 in elements[i1:]: els = sorted([el1, el2]) G.append({'type': 'G4', 'elements': els, 'eta': eta, 'gamma': gamma, 'zeta': zeta}) return G raise NotImplementedError('Unknown type: {}.'.format(type)) def make_default_symmetry_functions(elements): """Makes symmetry functions as in Nano Letters 14:2670, 2014. Parameters ---------- elements : list of str List of the elements, as in: ["C", "O", "H", "Cu"]. Returns ------- G : dict of lists The generated symmetry function parameters. """ G = {} for element0 in elements: # Radial symmetry functions. etas = [0.05, 4., 20., 80.] _G = [{'type': 'G2', 'element': element, 'eta': eta} for eta in etas for element in elements] # Angular symmetry functions. etas = [0.005] zetas = [1., 4.] gammas = [+1., -1.] for eta in etas: for zeta in zetas: for gamma in gammas: for i1, el1 in enumerate(elements): for el2 in elements[i1:]: els = sorted([el1, el2]) _G.append({'type': 'G4', 'elements': els, 'eta': eta, 'gamma': gamma, 'zeta': zeta}) G[element0] = _G return G def Kronecker(i, j): """Kronecker delta function. Parameters ---------- i : int First index of Kronecker delta. j : int Second index of Kronecker delta. Returns ------- int The value of the Kronecker delta. """ if i == j: return 1 else: return 0 def dRij_dRml_vector(i, j, m, l): """Returns the derivative of the position vector R_{ij} with respect to x_{l} of itomic index m. See Eq. 14d of the supplementary information of Khorshidi, Peterson, CPC(2016). Parameters ---------- i : int Index of the first atom. j : int Index of the second atom. m : int Index of the atom force is acting on. l : int Direction of force. Returns ------- list of float The derivative of the position vector R_{ij} with respect to x_{l} of atomic index m. """ if (m != i) and (m != j): return [0, 0, 0] else: dRij_dRml_vector = [None, None, None] c1 = Kronecker(m, j) - Kronecker(m, i) dRij_dRml_vector[0] = c1 * Kronecker(0, l) dRij_dRml_vector[1] = c1 * Kronecker(1, l) dRij_dRml_vector[2] = c1 * Kronecker(2, l) return dRij_dRml_vector def dRij_dRml(i, j, Ri, Rj, m, l): """Returns the derivative of the norm of position vector R_{ij} with respect to coordinate x_{l} of atomic index m. See Eq. 14c of the supplementary information of Khorshidi, Peterson, CPC(2016). Parameters ---------- i : int Index of the first atom. j : int Index of the second atom. Ri : float Position of the first atom. Rj : float Position of the second atom. m : int Index of the atom force is acting on. l : int Direction of force. Returns ------- dRij_dRml : list of float The derivative of the noRi of position vector R_{ij} with respect to x_{l} of atomic index m. """ Rij = np.linalg.norm(Rj - Ri) if m == i and i != j: # i != j is necessary for periodic systems dRij_dRml = -(Rj[l] - Ri[l]) / Rij elif m == j and i != j: # i != j is necessary for periodic systems dRij_dRml = (Rj[l] - Ri[l]) / Rij else: dRij_dRml = 0 return dRij_dRml def dCos_theta_ijk_dR_ml(i, j, k, Ri, Rj, Rk, m, l): """Returns the derivative of Cos(theta_{ijk}) with respect to x_{l} of atomic index m. See Eq. 14f of the supplementary information of Khorshidi, Peterson, CPC(2016). Parameters ---------- i : int Index of the center atom. j : int Index of the first atom. k : int Index of the second atom. Ri : float Position of the center atom. Rj : float Position of the first atom. Rk : float Position of the second atom. m : int Index of the atom force is acting on. l : int Direction of force. Returns ------- dCos_theta_ijk_dR_ml : float Derivative of Cos(theta_{ijk}) with respect to x_{l} of atomic index m. """ Rij_vector = Rj - Ri Rij = np.linalg.norm(Rij_vector) Rik_vector = Rk - Ri Rik = np.linalg.norm(Rik_vector) dCos_theta_ijk_dR_ml = 0 dRijdRml = dRij_dRml_vector(i, j, m, l) if np.array(dRijdRml).any() != 0: dCos_theta_ijk_dR_ml += np.dot(dRijdRml, Rik_vector) / (Rij * Rik) dRikdRml = dRij_dRml_vector(i, k, m, l) if np.array(dRikdRml).any() != 0: dCos_theta_ijk_dR_ml += np.dot(Rij_vector, dRikdRml) / (Rij * Rik) dRijdRml = dRij_dRml(i, j, Ri, Rj, m, l) if dRijdRml != 0: dCos_theta_ijk_dR_ml += - np.dot(Rij_vector, Rik_vector) * dRijdRml / \ ((Rij ** 2.) * Rik) dRikdRml = dRij_dRml(i, k, Ri, Rk, m, l) if dRikdRml != 0: dCos_theta_ijk_dR_ml += - np.dot(Rij_vector, Rik_vector) * dRikdRml / \ (Rij * (Rik ** 2.)) return dCos_theta_ijk_dR_ml def calculate_G2_prime(neighborindices, neighborsymbols, neighborpositions, G_element, eta, cutoff, i, Ri, m, l, fortran): """Calculates coordinate derivative of G2 symmetry function for atom at index i and position Ri with respect to coordinate x_{l} of atom index m. See Eq. 13b of the supplementary information of Khorshidi, Peterson, CPC(2016). Parameters --------- neighborindices : list of int List of int of neighboring atoms. neighborsymbols : list of str List of symbols of neighboring atoms. neighborpositions : list of list of float List of Cartesian atomic positions of neighboring atoms. G_element : dict Symmetry functions of the center atom. eta : float Parameter of Behler symmetry functions. cutoff : dict Cutoff function, typically from amp.descriptor.cutoffs. Should be also formatted as a dictionary by todict method, e.g. cutoff=Cosine(6.5).todict() i : int Index of the center atom. Ri : list Position of the center atom. Should be fed as a list of three floats. m : int Index of the atom force is acting on. l : int Direction of force. fortran : bool If True, will use the fortran subroutines, else will not. Returns ------- ridge : float Coordinate derivative of G2 symmetry function for atom at index a and position Ri with respect to coordinate x_{l} of atom index m. """ if fortran: # fortran version; faster G_number = [atomic_numbers[G_element]] neighbornumbers = \ [atomic_numbers[symbol] for symbol in neighborsymbols] if len(neighborpositions) == 0: ridge = 0. else: cutofffn = cutoff['name'] Rc = cutoff['kwargs']['Rc'] args_calculate_g2_prime = dict( neighborindices=list(neighborindices), neighbornumbers=neighbornumbers, neighborpositions=neighborpositions, g_number=G_number, g_eta=eta, rc=Rc, cutofffn=cutofffn, i=i, ri=Ri, m=m, l=l ) if cutofffn == 'Polynomial': args_calculate_g2_prime['p_gamma'] = cutoff['kwargs']['gamma'] ridge = fmodules.calculate_g2_prime(**args_calculate_g2_prime) else: Rc = cutoff['kwargs']['Rc'] cutoff_fxn = dict2cutoff(cutoff) ridge = 0. # One aspect of a fingerprint :) num_neighbors = len(neighborpositions) # number of neighboring atoms for count in range(num_neighbors): symbol = neighborsymbols[count] Rj = neighborpositions[count] j = neighborindices[count] if symbol == G_element: dRijdRml = dRij_dRml(i, j, Ri, Rj, m, l) if dRijdRml != 0: Rij = np.linalg.norm(Rj - Ri) args_cutoff_fxn = dict(Rij=Rij) if cutoff['name'] == 'Polynomial': args_cutoff_fxn['gamma'] = cutoff['kwargs']['gamma'] term1 = (-2. * eta * Rij * cutoff_fxn(**args_cutoff_fxn) / (Rc ** 2.) + cutoff_fxn.prime(**args_cutoff_fxn)) ridge += np.exp(- eta * (Rij ** 2.) / (Rc ** 2.)) * \ term1 * dRijdRml return ridge def calculate_G4_prime(neighborindices, neighborsymbols, neighborpositions, G_elements, gamma, zeta, eta, cutoff, i, Ri, m, l, fortran): """Calculates coordinate derivative of G4 symmetry function for atom at index i and position Ri with respect to coordinate x_{l} of atom index m. See Eq. 13d of the supplementary information of Khorshidi, Peterson, CPC(2016). Parameters ---------- neighborindices : list of int List of int of neighboring atoms. neighborsymbols : list of str List of symbols of neighboring atoms. neighborpositions : list of list of float List of Cartesian atomic positions of neighboring atoms. G_elements : list of str A list of two members, each member is the chemical species of one of the neighboring atoms forming the triangle with the center atom. gamma : float Parameter of Behler symmetry functions. zeta : float Parameter of Behler symmetry functions. eta : float Parameter of Behler symmetry functions. cutoff : dict Cutoff function, typically from amp.descriptor.cutoffs. Should be also formatted as a dictionary by todict method, e.g. cutoff=Cosine(6.5).todict() i : int Index of the center atom. Ri : list Position of the center atom. Should be fed as a list of three floats. m : int Index of the atom force is acting on. l : int Direction of force. fortran : bool If True, will use the fortran subroutines, else will not. Returns ------- ridge : float Coordinate derivative of G4 symmetry function for atom at index i and position Ri with respect to coordinate x_{l} of atom index m. """ if fortran: # fortran version; faster G_numbers = sorted([atomic_numbers[el] for el in G_elements]) neighbornumbers = [atomic_numbers[symbol] for symbol in neighborsymbols] if len(neighborpositions) == 0: ridge = 0. else: cutofffn = cutoff['name'] Rc = cutoff['kwargs']['Rc'] args_calculate_g4_prime = dict( neighborindices=list(neighborindices), neighbornumbers=neighbornumbers, neighborpositions=neighborpositions, g_numbers=G_numbers, g_gamma=gamma, g_zeta=zeta, g_eta=eta, rc=Rc, cutofffn=cutofffn, i=i, ri=Ri, m=m, l=l ) if cutofffn == 'Polynomial': args_calculate_g4_prime['p_gamma'] = cutoff['kwargs']['gamma'] ridge = fmodules.calculate_g4_prime(**args_calculate_g4_prime) else: Rc = cutoff['kwargs']['Rc'] cutoff_fxn = dict2cutoff(cutoff) ridge = 0. # number of neighboring atoms counts = range(len(neighborpositions)) for j in counts: for k in counts[(j + 1):]: els = sorted([neighborsymbols[j], neighborsymbols[k]]) if els != G_elements: continue Rj = neighborpositions[j] Rk = neighborpositions[k] Rij_vector = neighborpositions[j] - Ri Rij = np.linalg.norm(Rij_vector) Rik_vector = neighborpositions[k] - Ri Rik = np.linalg.norm(Rik_vector) Rjk_vector = neighborpositions[k] - neighborpositions[j] Rjk = np.linalg.norm(Rjk_vector) cos_theta_ijk = np.dot(Rij_vector, Rik_vector) / Rij / Rik c1 = (1. + gamma * cos_theta_ijk) _Rij = dict(Rij=Rij) _Rik = dict(Rij=Rik) _Rjk = dict(Rij=Rjk) if cutoff['name'] == 'Polynomial': _Rij['gamma'] = cutoff['kwargs']['gamma'] _Rik['gamma'] = cutoff['kwargs']['gamma'] _Rjk['gamma'] = cutoff['kwargs']['gamma'] fcRij = cutoff_fxn(**_Rij) fcRik = cutoff_fxn(**_Rik) fcRjk = cutoff_fxn(**_Rjk) if zeta == 1: term1 = \ np.exp(- eta * (Rij ** 2. + Rik ** 2. + Rjk ** 2.) / (Rc ** 2.)) else: term1 = c1 ** (zeta - 1.) * \ np.exp(- eta * (Rij ** 2. + Rik ** 2. + Rjk ** 2.) / (Rc ** 2.)) term2 = 0. fcRijfcRikfcRjk = fcRij * fcRik * fcRjk dCosthetadRml = dCos_theta_ijk_dR_ml(i, neighborindices[j], neighborindices[k], Ri, Rj, Rk, m, l) if dCosthetadRml != 0: term2 += gamma * zeta * dCosthetadRml dRijdRml = dRij_dRml(i, neighborindices[j], Ri, Rj, m, l) if dRijdRml != 0: term2 += -2. * c1 * eta * Rij * dRijdRml / (Rc ** 2.) dRikdRml = dRij_dRml(i, neighborindices[k], Ri, Rk, m, l) if dRikdRml != 0: term2 += -2. * c1 * eta * Rik * dRikdRml / (Rc ** 2.) dRjkdRml = dRij_dRml(neighborindices[j], neighborindices[k], Rj, Rk, m, l) if dRjkdRml != 0: term2 += -2. * c1 * eta * Rjk * dRjkdRml / (Rc ** 2.) term3 = fcRijfcRikfcRjk * term2 term4 = cutoff_fxn.prime(**_Rij) * dRijdRml * fcRik * fcRjk term5 = fcRij * cutoff_fxn.prime(**_Rik) * dRikdRml * fcRjk term6 = fcRij * fcRik * cutoff_fxn.prime(**_Rjk) * dRjkdRml ridge += term1 * (term3 + c1 * (term4 + term5 + term6)) ridge *= 2. ** (1. - zeta) return ridge if __name__ == "__main__": """Directly calling this module; apparently from another node. Calls should come as python -m amp.descriptor.gaussian id hostname:port This session will then start a zmq session with that socket, labeling itself with id. Instructions on what to do will come from the socket. """ import sys import tempfile import zmq from ..utilities import MessageDictionary fortran = False if fmodules is None else True hostsocket = sys.argv[-1] proc_id = sys.argv[-2] msg = MessageDictionary(proc_id) # Send standard lines to stdout signaling process started and where # error is directed. This should be caught by pxssh. (This could # alternatively be done by zmq, but this works.) print('') # Signal that program started. sys.stderr = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.stderr') print('Log and error written to %s' % sys.stderr.name) sys.stderr.write('initiated\n') sys.stderr.flush() # Establish client session via zmq; find purpose. context = zmq.Context() sys.stderr.write('context started\n') sys.stderr.flush() socket = context.socket(zmq.REQ) sys.stderr.write('socket started\n') sys.stderr.flush() socket.connect('tcp://%s' % hostsocket) sys.stderr.write('connection made\n') sys.stderr.flush() socket.send_pyobj(msg('')) sys.stderr.write('message sent\n') sys.stderr.flush() purpose = socket.recv_pyobj() sys.stderr.write('purpose received\n') sys.stderr.flush() sys.stderr.write('purpose: %s \n' % purpose) sys.stderr.flush() if purpose == 'calculate_neighborlists': # Request variables. socket.send_pyobj(msg('', 'cutoff')) cutoff = socket.recv_pyobj() socket.send_pyobj(msg('', 'images')) images = socket.recv_pyobj() # sys.stderr.write(str(images)) # Just to see if they are there. # Perform the calculations. calc = NeighborlistCalculator(cutoff=cutoff) neighborlist = {} # for key in images.iterkeys(): while len(images) > 0: key, image = images.popitem() # Reduce memory. neighborlist[key] = calc.calculate(image, key) # Send the results. socket.send_pyobj(msg('', neighborlist)) socket.recv_string() # Needed to complete REQ/REP. elif purpose == 'calculate_fingerprints': # Request variables. socket.send_pyobj(msg('', 'cutoff')) cutoff = socket.recv_pyobj() socket.send_pyobj(msg('', 'Gs')) Gs = socket.recv_pyobj() socket.send_pyobj(msg('', 'neighborlist')) neighborlist = socket.recv_pyobj() socket.send_pyobj(msg('', 'images')) images = socket.recv_pyobj() calc = FingerprintCalculator(neighborlist, Gs, cutoff, fortran) result = {} while len(images) > 0: key, image = images.popitem() # Reduce memory. result[key] = calc.calculate(image, key) if len(images) % 100 == 0: socket.send_pyobj(msg('', len(images))) socket.recv_string() # Needed to complete REQ/REP. # Send the results. socket.send_pyobj(msg('', result)) socket.recv_string() # Needed to complete REQ/REP. elif purpose == 'calculate_fingerprint_primes': # Request variables. socket.send_pyobj(msg('', 'cutoff')) cutoff = socket.recv_pyobj() socket.send_pyobj(msg('', 'Gs')) Gs = socket.recv_pyobj() socket.send_pyobj(msg('', 'neighborlist')) neighborlist = socket.recv_pyobj() socket.send_pyobj(msg('', 'images')) images = socket.recv_pyobj() calc = FingerprintPrimeCalculator(neighborlist, Gs, cutoff, fortran) result = {} while len(images) > 0: key, image = images.popitem() # Reduce memory. result[key] = calc.calculate(image, key) if len(images) % 100 == 0: socket.send_pyobj(msg('', len(images))) socket.recv_string() # Needed to complete REQ/REP. # Send the results. socket.send_pyobj(msg('', result)) socket.recv_string() # Needed to complete REQ/REP. else: raise NotImplementedError('purpose %s unknown.' % purpose) amp-0.6/amp/Makefile0000644000175000017500000000113513137634440014236 0ustar muammarmuammarpython2: +$(MAKE) -C model +$(MAKE) -C descriptor f2py -c -m fmodules model.f90 descriptor/cutoffs.f90 descriptor/gaussian.f90 descriptor/zernike.f90 model/neuralnetwork.f90 python3: +$(MAKE) -C model +$(MAKE) -C descriptor f2py3 -c -m fmodules model.f90 descriptor/cutoffs.f90 descriptor/gaussian.f90 descriptor/zernike.f90 model/neuralnetwork.f90 AMPDIR:=$(shell pwd) py2tests: rm -fr /tmp/py2_amptests mkdir -p /tmp/py2_amptests cd /tmp/py2_amptests && nosetests $(AMPDIR)/.. py3tests: rm -fr /tmp/py3_amptests mkdir -p /tmp/py3_amptests cd /tmp/py3_amptests && nosetests3 $(AMPDIR)/.. amp-0.6/amp/stats/0002755000175000017500000000000013140115602013722 5ustar muammarmuammaramp-0.6/amp/stats/__init__.py0000644000175000017500000000000013137634440016033 0ustar muammarmuammaramp-0.6/amp/stats/bootstrap.py0000644000175000017500000003567113137634440016337 0ustar muammarmuammarimport os import sys import shutil import numpy as np from string import Template import time import json from StringIO import StringIO from scipy.stats.mstats import mquantiles import tarfile import tempfile import ase.io from ..utilities import hash_images, Logger from .. import Amp calc_text = """ from amp import Amp from amp.descriptor.gaussian import Gaussian from amp.model.neuralnetwork import NeuralNetwork calc = Amp(descriptor=Gaussian(), model=NeuralNetwork(), dblabel='../amp-db') """ train_line = "calc.train(images=hashed_images)" script = """#!/usr/bin/env python ${headerlines} from amp.utilities import TrainingConvergenceError, hash_images from ase.parallel import paropen from bunquant.bootstrap import hash_with_duplicates import os ${calc_text} ensemble_index = int(os.path.split(os.getcwd())[-1]) trainfile = '../training-images/%i.traj' % ensemble_index hashed_images = hash_with_duplicates(trainfile) converged = True try: ${train_line} except TrainingConvergenceError: converged = False f = paropen('converged', 'w') f.write(str(converged)) f.close() """ class BootStrap: """A bootstrap ensemble calculator which serves as a wrapper around and Amp calculator. Initiate with an amp.utilities.Logger instance as log. If an existing trained bootstrap calculator is available, it can be loaded by providing its filename to the load keyword. Note that the 'train' method is meant to be a job-submission and -management script; e.g., it will typically be run at the command line to both submit jobs and monitor their convergence. """ def __init__(self, load=None, log=None): if log is None: log = Logger(sys.stdout) self.log = log if load is None: return with open(load) as f: calctexts = json.load(f) self.ensemble = [] for calctext in calctexts: f = StringIO(calctext) calc = Amp.load(file=f) calc.log = Logger(None) self.ensemble.append(calc) log('Loaded ensemble of %i calculators.' % len(self.ensemble)) def train(self, images, n=50, calc_text=calc_text, headerlines='', start_command='python run.py', sleep=0.1, train_line=train_line, label='bootstrap'): """Trains a bootstrap ensemble of calculators. This is set up to enable the submision of each as a job through the local queuing system, but can also run in serial. On first call to this method, jobs are created/submitted. On subsequent calls, jobs are analyzed for convergence. If all are converged, an ensemble is created and the training directory is archived. Creates a lot of individual runs in directories n: int size of ensemble (number of calculators to train) calc_text: text text that is used to initiate the Amp calculator. see the example in this module in calc_text; must produce a 'calc' object headerlines: text lines in the top of the python script that will be submitted this would typically contain comment lines for the batching system, such as '#SBATCH -n=3\n #SBATCH -cores=8\n...' start_command: text command to start the job in the current queuing system, such as 'sbatch run.py' ('run.py' is the scriptname here) for serial operation use 'python run.py' sleep : float time (s) to sleep between job submissions train_line: text line to use to train each amp instance; usually the default is fine but user may want to use this to insert additional keywords such as train_forces=False label: string label to give final trained calculator """ log = self.log trainingpath = '-'.join((label, 'training')) if os.path.exists(trainingpath): log('Path exists. Checking for which jobs are finished.') n_unfinished = 0 n_converged = 0 n_unconverged = 0 pwd = os.getcwd() os.chdir(trainingpath) fulltrainingpath = os.getcwd() for index in range(n): os.chdir('%i' % index) if not os.path.exists('converged'): log('%i: Still running? No converged file.' % index) os.chdir(fulltrainingpath) n_unfinished += 1 continue with open('converged') as f: converged = f.read() if converged == 'True': log('%i: Converged.' % index) n_converged += 1 else: log('%i: Not converged. Cleaning up directory to ' ' restart job.' % index) n_unconverged += 1 for _ in os.listdir(os.getcwd()): if _ != 'run.py': if os.path.isdir(_): shutil.rmtree(_) else: os.remove(_) os.system(start_command) time.sleep(sleep) log('%i: Restarted.') os.chdir(fulltrainingpath) log('') log('Stats:') log('%10i converged' % n_converged) log('%10i did not converge, restarted' % n_unconverged) log('%10i apparently still running' % n_unfinished) log('=' * 10) log('%10i total' % n) log('\n') if n_converged < n: log('Not all runs converged; not creating bundled amp ' 'calculator.') return log('Creating bundled amp calculator.') ensemble = [] for index in range(n): os.chdir('%i' % index) with open('amp.amp') as f: text = f.read() ensemble.append(text) os.chdir(fulltrainingpath) os.chdir(pwd) with open('%s.ensemble' % label, 'w') as f: json.dump(ensemble, f) log('Saved in json format as "%s.ensemble".' % label) log('Converting training directory into tar archive...') archive_directory(trainingpath) log('...converted.') return log('Training set: ' + str(images)) images = hash_images(images) log('%i images in training set after hashing.' % len(images)) image_keys = images.keys() originalpath = os.getcwd() trajpath = os.path.join(trainingpath, 'training-images') os.mkdir(trainingpath) os.mkdir(trajpath) log('Creating bootstrapped training images in %s.' % trajpath) for index in range(n): log(' Choosing images for %i.' % index) chosen = bootstrap(image_keys) log(' Writing trajectory for %i.' % index) traj = ase.io.Trajectory( os.path.join(trajpath, '%i.traj' % index), 'w') for key in chosen: traj.write(images[key]) log('Creating and submitting jobs.') os.chdir(trainingpath) template = Template(script) pwd = os.getcwd() for index in range(n): os.mkdir('%i' % index) os.chdir('%i' % index) with open('run.py', 'w') as f: f.write(template.substitute({'headerlines': headerlines, 'calc_text': calc_text, 'train_line': train_line})) os.system(start_command) time.sleep(sleep) os.chdir(pwd) os.chdir(originalpath) def get_potential_energy(self, atoms, output=(.5,)): """Returns the potential energy from the ensemble for the atoms object. By default only returns the median prediction (50th percentile) of the ensemble, such that it works like a normal ASE calculator. To get uncertainty information, use the output keyword with the following codes: : (where is a float) return the q quantile of the ensemble (where the quantile is a decimal, as in 0.5 for 50th percentile) e: return the whole ensemble prediction as a list Join the arguments with commas. For example, to return the median prediction plus a centered spread covering 90% of the ensemble prediction, use output=[.5, .05, .95]. If the ensemble is requested, it must be the last argument, e.g., output=[.5, .025, .97.5, 'e']. Note a list is typically returned, but if only one attribute is requested it returns it as a float, so that it's ASE-like. """ energies = [calc.get_potential_energy(atoms) for calc in self.ensemble] if output[-1] == 'e': quantiles = output[:-1] return_ensemble = True else: quantiles = output return_ensemble = False for quantile in quantiles: if (quantile > 1.0) or (quantile < 0.0): raise RuntimeError('Quantiles must be between 0 and 1.') result = mquantiles(energies, prob=quantiles) result = list(result) if return_ensemble: result.append(energies) if len(result) == 1: result == result[0] return result def get_forces(self, atoms, output=(.5,)): """Returns the atomic forces from the ensemble for the atoms object. By default only returns the median prediction (50th percentile) of the ensemble, such that it works like a normal ASE calculator. To get uncertainty information, use the output keyword with the following codes: : (where is a float) return the q quantile of the ensemble (where the quantile is a decimal, as in 0.5 for 50th percentile) e: return the whole ensemble prediction as a list Join the arguments with commas. For example, to return the median prediction plus a centered spread covering 90% of the ensemble prediction, use output=[.5, .05, .95]. If the ensemble is requested, it must be the last argument, e.g., output=[.5, .025, .97.5, 'e']. Note a list is typically returned, but if only one attribute is requested it returns it as a float, so that it's ASE-like. """ forces = [calc.get_forces(atoms) for calc in self.ensemble] forces = np.array(forces) if output[-1] == 'e': quantiles = output[:-1] return_ensemble = True else: quantiles = output return_ensemble = False for quantile in quantiles: if (quantile > 1.0) or (quantile < 0.0): raise RuntimeError('Quantiles must be between 0 and 1.') # FIXME/ap: Had to switch to np.percentile from scipy mquantiles. # Because mquantiles doesn't support higher dimensions. # Should probably switch to percentiles throughout the code as # it's easier to read. percentiles = np.array(quantiles) * 100. result = np.percentile(forces, percentiles, axis=0) result = list(result) if return_ensemble: result.append(forces) if len(result) == 1: result == result[0] return result def get_atomic_energies(self, atoms, output=(.5,)): """ Returns the energy per atom from ensemble. The output parameter works as get_potential_energy.""" if output[-1] == 'e': quantiles = output[:-1] return_ensemble = True else: quantiles = output return_ensemble = False for quantile in quantiles: if (quantile > 1.0) or (quantile < 0.0): raise RuntimeError('Percentiles must be between 0 and 1.') self.get_potential_energy(atoms) # Assure calculation is fresh. atomic_energies = np.array([calc.model.atomic_energies for calc in self.ensemble]) result = mquantiles(atomic_energies, prob=quantiles, axis=0) result = list(result) if return_ensemble: result.append(atomic_energies) if len(result) == 1: result == result[0] return result def bootstrap(vector, size=None, return_missing=False): """Returns a randomly chosen, with replacement, version of the data set. If size is None returns a vector of same length. To pull from sample from multiple vectors, zip and unzip them like: >>> xsbs, ysbs = zip(*bootstrap(zip(xs, ys))) If return_missing == True, also finds and returns the missing elements not sampled from the vector as a second output. """ size = len(vector) if size is None else size ids = np.random.choice(len(vector), size=size, replace=True) chosen = [vector[_] for _ in ids] if return_missing is False: return chosen unchosen = set(range(len(vector))).difference(set(ids)) unchosen = [vector[_] for _ in unchosen] return chosen, unchosen def hash_with_duplicates(images): """Creates new hash id's for duplicate images; new dictionary contains a redundant copy of each atoms object, so that the lossfunctions can be used as-is. Note will typically waste ~30% of the computational cost; it would be more efficient to update the calls inside the loss functions.""" if not hasattr(images, 'keys'): images = hash_images(images) duplicates = images.metadata['duplicates'] dict_images = dict(images) for oldhash, repititions in duplicates.iteritems(): for repitition in range(repititions - 1): newhash = '-'.join([oldhash, '%i' % (repitition + 1)]) assert newhash not in dict_images dict_images[newhash] = images[oldhash] return dict_images def archive_directory(source_dir): """Turns into a .tar.gz file and removes the original directory.""" outputname = source_dir + '.tar.gz' if os.path.exists(outputname): raise RuntimeError('%s exists.' % outputname) with tarfile.open(outputname, 'w:gz') as tar: tar.add(source_dir) shutil.rmtree(source_dir) class TrainingArchive: """Helper to get training trajectories and Amp calc instances from the training tar ball. Initialize with archive name. The get commands use the path the file would have had if the archive were extracted.""" def __init__(self, name): self.tf = tarfile.open(name) def get_trajectory(self, path): # Doesn't work with extractfile because of numpy bug. tempdir = tempfile.mkdtemp() self.tf.extract(member=path, path=tempdir) return ase.io.Trajectory(os.path.join(tempdir, path)) def get_amp_calc(self, path): return Amp.load(self.tf.extractfile(path)) amp-0.6/amp/model/0002755000175000017500000000000013142666751013706 5ustar muammarmuammaramp-0.6/amp/model/__init__.py0000755000175000017500000014127613137634440016025 0ustar muammarmuammarimport sys import numpy as np from ase.calculators.calculator import Parameters from ..utilities import (Logger, ConvergenceOccurred, make_sublists, now, setup_parallel) try: from .. import fmodules except ImportError: fmodules = None class Model(object): """Class that includes common methods between different models.""" @property def log(self): """Method to set or get a logger. Should be an instance of amp.utilities.Logger. Parameters ---------- log : Logger object Write function at which to log data. Note this must be a callable function. """ if hasattr(self, '_log'): return self._log if hasattr(self.parent, 'log'): return self.parent.log return Logger(None) @log.setter def log(self, log): self._log = log def tostring(self): """Returns an evaluatable representation of the calculator that can be used to re-establish the calculator.""" # Make sure numpy prints out enough data. np.set_printoptions(precision=30, threshold=999999999) return self.parameters.tostring() def calculate_energy(self, fingerprints): """Calculates the model-predicted energy for an image, based on its fingerprint. Parameters ---------- fingerprints : dict Dictionary with images hashs as keys and the corresponding fingerprints as values. """ if self.parameters.mode == 'image-centered': raise NotImplementedError('This needs to be coded.') elif self.parameters.mode == 'atom-centered': self.atomic_energies = [] energy = 0.0 for index, (symbol, afp) in enumerate(fingerprints): atom_energy = self.calculate_atomic_energy(afp=afp, index=index, symbol=symbol) self.atomic_energies.append(atom_energy) energy += atom_energy return energy def calculate_forces(self, fingerprints, fingerprintprimes): """Calculates the model-predicted forces for an image, based on derivatives of fingerprints. Parameters ---------- fingerprints : dict Dictionary with images hashs as keys and the corresponding fingerprints as values. fingerprintprimes : dict Dictionary with images hashs as keys and the corresponding fingerprint derivatives as values. """ if self.parameters.mode == 'image-centered': raise NotImplementedError('This needs to be coded.') elif self.parameters.mode == 'atom-centered': selfindices = set([key[0] for key in fingerprintprimes.keys()]) forces = np.zeros((len(selfindices), 3)) for key in fingerprintprimes.keys(): selfindex, selfsymbol, nindex, nsymbol, i = key derafp = fingerprintprimes[key] afp = fingerprints[nindex][1] dforce = self.calculate_force(afp=afp, derafp=derafp, nindex=nindex, nsymbol=nsymbol, direction=i,) forces[selfindex][i] += dforce return forces def calculate_dEnergy_dParameters(self, fingerprints): """Calculates a list of floats corresponding to the derivative of model-predicted energy of an image with respect to model parameters. Parameters ---------- fingerprints : dict Dictionary with images hashs as keys and the corresponding fingerprints as values. """ if self.parameters.mode == 'image-centered': raise NotImplementedError('This needs to be coded.') elif self.parameters.mode == 'atom-centered': denergy_dparameters = None for index, (symbol, afp) in enumerate(fingerprints): temp = self.calculate_dAtomicEnergy_dParameters(afp=afp, index=index, symbol=symbol) if denergy_dparameters is None: denergy_dparameters = temp else: denergy_dparameters += temp return denergy_dparameters def calculate_numerical_dEnergy_dParameters(self, fingerprints, d=0.00001): """Evaluates dEnergy_dParameters using finite difference. This will trigger two calls to calculate_energy(), with each parameter perturbed plus/minus d. Parameters ---------- fingerprints : dict Dictionary with images hashs as keys and the corresponding fingerprints as values. d : float The amount of perturbation in each parameter. """ if self.parameters.mode == 'image-centered': raise NotImplementedError('This needs to be coded.') elif self.parameters.mode == 'atom-centered': vector = self.vector denergy_dparameters = [] for _ in range(len(vector)): vector[_] += d self.vector = vector eplus = self.calculate_energy(fingerprints) vector[_] -= 2 * d self.vector = vector eminus = self.calculate_energy(fingerprints) denergy_dparameters += [(eplus - eminus) / (2 * d)] vector[_] += d self.vector = vector denergy_dparameters = np.array(denergy_dparameters) return denergy_dparameters def calculate_dForces_dParameters(self, fingerprints, fingerprintprimes): """Calculates an array of floats corresponding to the derivative of model-predicted atomic forces of an image with respect to model parameters. Parameters ---------- fingerprints : dict Dictionary with images hashs as keys and the corresponding fingerprints as values. fingerprintprimes : dict Dictionary with images hashs as keys and the corresponding fingerprint derivatives as values. """ if self.parameters.mode == 'image-centered': raise NotImplementedError('This needs to be coded.') elif self.parameters.mode == 'atom-centered': selfindices = set([key[0] for key in fingerprintprimes.keys()]) dforces_dparameters = {(selfindex, i): None for selfindex in selfindices for i in range(3)} for key in fingerprintprimes.keys(): selfindex, selfsymbol, nindex, nsymbol, i = key derafp = fingerprintprimes[key] afp = fingerprints[nindex][1] temp = self.calculate_dForce_dParameters(afp=afp, derafp=derafp, direction=i, nindex=nindex, nsymbol=nsymbol,) if dforces_dparameters[(selfindex, i)] is None: dforces_dparameters[(selfindex, i)] = temp else: dforces_dparameters[(selfindex, i)] += temp return dforces_dparameters def calculate_numerical_dForces_dParameters(self, fingerprints, fingerprintprimes, d=0.00001): """Evaluates dForces_dParameters using finite difference. This will trigger two calls to calculate_forces(), with each parameter perturbed plus/minus d. Parameters --------- fingerprints : dict Dictionary with images hashs as keys and the corresponding fingerprints as values. fingerprintprimes : dict Dictionary with images hashs as keys and the corresponding fingerprint derivatives as values. d : float The amount of perturbation in each parameter. """ if self.parameters.mode == 'image-centered': raise NotImplementedError('This needs to be coded.') elif self.parameters.mode == 'atom-centered': selfindices = set([key[0] for key in fingerprintprimes.keys()]) dforces_dparameters = {(selfindex, i): [] for selfindex in selfindices for i in range(3)} vector = self.vector for _ in range(len(vector)): vector[_] += d self.vector = vector fplus = self.calculate_forces(fingerprints, fingerprintprimes) vector[_] -= 2 * d self.vector = vector fminus = self.calculate_forces(fingerprints, fingerprintprimes) for selfindex in selfindices: for i in range(3): dforces_dparameters[(selfindex, i)] += \ [(fplus[selfindex][i] - fminus[selfindex][i]) / ( 2 * d)] vector[_] += d self.vector = vector for selfindex in selfindices: for i in range(3): dforces_dparameters[(selfindex, i)] = \ np.array(dforces_dparameters[(selfindex, i)]) return dforces_dparameters class LossFunction: """Basic loss function, which can be used by the model.get_loss method which is required in standard model classes. This version is pure python and thus will be slow compared to a fortran/parallel implementation. If parallel is None, it will pull it from the model itself. Only use this keyword to override the model's specification. Also has parallelization methods built in. See self.default_parameters for the default values of parameters specified as None. Parameters ---------- energy_coefficient : float Coefficient of the energy contribution in the loss function. force_coefficient : float Coefficient of the force contribution in the loss function. Can set to None as shortcut to turn off force training. convergence : dict Dictionary of keys and values defining convergence. Keys are 'energy_rmse', 'energy_maxresid', 'force_rmse', and 'force_maxresid'. If 'force_rmse' and 'force_maxresid' are both set to None, force training is turned off and force_coefficient is set to None. parallel : dict Parallel configuration dictionary. Will pull from model itself if not specified. overfit : float Multiplier of the weights norm penalty term in the loss function. raise_ConvergenceOccurred : bool If True will raise convergence notice. log_losses : bool If True will log the loss function value in the log file else will not. d : None or float If d is None, both loss function and its gradient are calculated analytically. If d is a float, then gradient of the loss function is calculated by perturbing each parameter plus/minus d. """ default_parameters = {'convergence': {'energy_rmse': 0.001, 'energy_maxresid': None, 'force_rmse': None, 'force_maxresid': None, } } def __init__(self, energy_coefficient=1.0, force_coefficient=0.04, convergence=None, parallel=None, overfit=0., raise_ConvergenceOccurred=True, log_losses=True, d=None): p = self.parameters = Parameters( {'importname': '.model.LossFunction'}) # 'dict' creates a copy; otherwise mutable in class. c = p['convergence'] = dict(self.default_parameters['convergence']) if convergence is not None: for key, value in convergence.items(): p['convergence'][key] = value p['energy_coefficient'] = energy_coefficient p['force_coefficient'] = force_coefficient p['overfit'] = overfit self.raise_ConvergenceOccurred = raise_ConvergenceOccurred self.log_losses = log_losses self.d = d self._step = 0 self._initialized = False self._data_sent = False self._parallel = parallel if (c['force_rmse'] is None) and (c['force_maxresid'] is None): p['force_coefficient'] = None if p['force_coefficient'] is None: c['force_rmse'] = None c['force_maxresid'] = None def attach_model(self, model, fingerprints=None, fingerprintprimes=None, images=None): """Attach the model to be used to the loss function. fingerprints and training images need not be supplied if they are already attached to the model via model.trainingparameters. Parameters ---------- model : object Class representing the regression model. fingerprints : dict Dictionary with images hashs as keys and the corresponding fingerprints as values. fingerprintprimes : dict Dictionary with images hashs as keys and the corresponding fingerprint derivatives as values. images : list or str List of ASE atoms objects with positions, symbols, energies, and forces in ASE format. This is the training set of data. This can also be the path to an ASE trajectory (.traj) or database (.db) file. Energies can be obtained from any reference, e.g. DFT calculations. """ self._model = model self.fingerprints = fingerprints self.fingerprintprimes = fingerprintprimes self.images = images def _initialize(self): """Procedures to be run on the first call only, such as establishing SSH sessions, etc.""" if self._initialized is True: return if self._parallel is None: self._parallel = self._model._parallel log = self._model.log if self.fingerprints is None: self.fingerprints = \ self._model.trainingparameters.descriptor.fingerprints # May also make sense to decide whether or not to calculate # fingerprintprimes based on the value of train_forces. if ((self.parameters.force_coefficient is not None) and (self.fingerprintprimes is None)): self.fingerprintprimes = \ self._model.trainingparameters.descriptor.fingerprintprimes if self.images is None: self.images = self._model.trainingparameters.trainingimages if self._parallel['cores'] != 1: # Initialize workers. python = sys.executable workercommand = '%s -m %s' % (python, self.__module__) server, connections, n_pids = setup_parallel(self._parallel, workercommand, log) self._sessions = {'master': server, 'connections': connections, # SSH's/nodes 'n_pids': n_pids} # total no. of workers if self.log_losses: p = self.parameters convergence = p['convergence'] log(' Loss function convergence criteria:') log(' energy_rmse: ' + str(convergence['energy_rmse'])) log(' energy_maxresid: ' + str(convergence['energy_maxresid'])) log(' force_rmse: ' + str(convergence['force_rmse'])) log(' force_maxresid: ' + str(convergence['force_maxresid'])) log(' Loss function set-up:') log(' energy_coefficient: ' + str(p.energy_coefficient)) log(' force_coefficient: ' + str(p.force_coefficient)) log(' overfit: ' + str(p.overfit)) log('\n') if p.force_coefficient is None: header = '%5s %19s %12s %12s %12s' log(header % ('', '', '', '', 'Energy')) log(header % ('Step', 'Time', 'Loss (SSD)', 'EnergyRMSE', 'MaxResid')) log(header % ('=' * 5, '=' * 19, '=' * 12, '=' * 12, '=' * 12)) else: header = '%5s %19s %12s %12s %12s %12s %12s' log(header % ('', '', '', '', 'Energy', '', 'Force')) log(header % ('Step', 'Time', 'Loss (SSD)', 'EnergyRMSE', 'MaxResid', 'ForceRMSE', 'MaxResid')) log(header % ('=' * 5, '=' * 19, '=' * 12, '=' * 12, '=' * 12, '=' * 12, '=' * 12)) self._initialized = True def _send_data_to_fortran(self,): """Procedures to be run in fortran mode for a single requested core only. Also just on the first call for sending data to fortran modules. """ if self._data_sent is True: return num_images = len(self.images) p = self.parameters energy_coefficient = p.energy_coefficient overfit = p.overfit if p.force_coefficient is None: train_forces = False force_coefficient = 0. else: train_forces = True force_coefficient = p.force_coefficient mode = self._model.parameters.mode if mode == 'atom-centered': num_atoms = None elif mode == 'image-centered': raise NotImplementedError('Image-centered mode is not coded yet.') (actual_energies, actual_forces, elements, atomic_positions, num_images_atoms, atomic_numbers, raveled_fingerprints, num_neighbors, raveled_neighborlists, raveled_fingerprintprimes) = (None,) * 10 value = ravel_data(train_forces, mode, self.images, self.fingerprints, self.fingerprintprimes,) if mode == 'image-centered': if not train_forces: (actual_energies, atomic_positions) = value else: (actual_energies, actual_forces, atomic_positions) = value else: if not train_forces: (actual_energies, elements, num_images_atoms, atomic_numbers, raveled_fingerprints) = value else: (actual_energies, actual_forces, elements, num_images_atoms, atomic_numbers, raveled_fingerprints, num_neighbors, raveled_neighborlists, raveled_fingerprintprimes) = value send_data_to_fortran(fmodules, energy_coefficient, force_coefficient, overfit, train_forces, num_atoms, num_images, actual_energies, actual_forces, atomic_positions, num_images_atoms, atomic_numbers, raveled_fingerprints, num_neighbors, raveled_neighborlists, raveled_fingerprintprimes, self._model, self.d) self._data_sent = True def _cleanup(self): """Closes SSH sessions.""" self._initialized = False if not hasattr(self, '_sessions'): return server = self._sessions['master'] finished = np.array([False] * self._sessions['n_pids']) while not finished.all(): message = server.recv_pyobj() if (message['subject'] == '' and message['data'] == 'parameters'): server.send_pyobj('') finished[int(message['id'])] = True for _ in self._sessions['connections']: if hasattr(_, 'logout'): _.logout() del self._sessions['connections'] def get_loss(self, parametervector, lossprime): """Returns the current value of the loss function for a given set of parameters, or, if the energy is less than the energy_tol raises a ConvergenceException. Parameters ---------- parametervector : list Parameters of the regression model in the form of a list. lossprime : bool If True, will calculate and return dloss_dparameters, else will only return zero for dloss_dparameters. """ self._initialize() if self._parallel['cores'] == 1: if self._model.fortran: self._model.vector = parametervector self._send_data_to_fortran() (loss, dloss_dparameters, energy_loss, force_loss, energy_maxresid, force_maxresid) = \ fmodules.calculate_loss(parameters=parametervector, num_parameters=len( parametervector), lossprime=lossprime) else: loss, dloss_dparameters, energy_loss, force_loss, \ energy_maxresid, force_maxresid = \ self.calculate_loss(parametervector, lossprime=lossprime) else: server = self._sessions['master'] n_pids = self._sessions['n_pids'] # Subdivide tasks. keys = make_sublists(self.images.keys(), n_pids) args = {'lossprime': lossprime, 'd': self.d} results = self.process_parallels(parametervector, server, n_pids, keys, args=args) loss = results['loss'] dloss_dparameters = results['dloss_dparameters'] energy_loss = results['energy_loss'] force_loss = results['force_loss'] energy_maxresid = results['energy_maxresid'] force_maxresid = results['force_maxresid'] self.loss, self.energy_loss, self.force_loss, \ self.energy_maxresid, self.force_maxresid = \ loss, energy_loss, force_loss, energy_maxresid, force_maxresid if lossprime: self.dloss_dparameters = dloss_dparameters if self.raise_ConvergenceOccurred: # Only during calculation of loss function (and not lossprime) # convergence is checked and values are printed out in the log # file. if lossprime is False: self._model.vector = parametervector converged = self.check_convergence(loss, energy_loss, force_loss, energy_maxresid, force_maxresid) if converged: self._cleanup() if self._parallel['cores'] != 1: # Needed to properly close socket connection # (python3). server.close() raise ConvergenceOccurred() return {'loss': self.loss, 'dloss_dparameters': (self.dloss_dparameters if lossprime is True else dloss_dparameters), 'energy_loss': self.energy_loss, 'force_loss': self.force_loss, 'energy_maxresid': self.energy_maxresid, 'force_maxresid': self.force_maxresid, } def calculate_loss(self, parametervector, lossprime): """Method that calculates the loss, derivative of the loss with respect to parameters (if requested), and max_residual. Parameters ---------- parametervector : list Parameters of the regression model in the form of a list. lossprime : bool If True, will calculate and return dloss_dparameters, else will only return zero for dloss_dparameters. """ self._model.vector = parametervector p = self.parameters energyloss = 0. forceloss = 0. energy_maxresid = 0. force_maxresid = 0. dloss_dparameters = np.array([0.] * len(parametervector)) model = self._model for hash in self.images.keys(): image = self.images[hash] no_of_atoms = len(image) amp_energy = model.calculate_energy(self.fingerprints[hash]) actual_energy = image.get_potential_energy(apply_constraint=False) residual_per_atom = abs(amp_energy - actual_energy) / \ len(image) if residual_per_atom > energy_maxresid: energy_maxresid = residual_per_atom energyloss += residual_per_atom**2 # Calculates derivative of the loss function with respect to # parameters if lossprime is true if lossprime: if model.parameters.mode == 'image-centered': raise NotImplementedError('This needs to be coded.') elif model.parameters.mode == 'atom-centered': if self.d is None: denergy_dparameters = \ model.calculate_dEnergy_dParameters( self.fingerprints[hash]) else: denergy_dparameters = \ model.calculate_numerical_dEnergy_dParameters( self.fingerprints[hash], d=self.d) temp = p.energy_coefficient * 2. * \ (amp_energy - actual_energy) * \ denergy_dparameters / \ (no_of_atoms ** 2.) dloss_dparameters += temp if p.force_coefficient is not None: amp_forces = \ model.calculate_forces(self.fingerprints[hash], self.fingerprintprimes[hash]) actual_forces = image.get_forces(apply_constraint=False) for index in range(no_of_atoms): for i in range(3): force_resid = abs(amp_forces[index][i] - actual_forces[index][i]) if force_resid > force_maxresid: force_maxresid = force_resid temp = (1. / 3.) * (amp_forces[index][i] - actual_forces[index][i]) ** 2. / \ no_of_atoms forceloss += temp # Calculates derivative of the loss function with respect to # parameters if lossprime is true if lossprime: if model.parameters.mode == 'image-centered': raise NotImplementedError('This needs to be coded.') elif model.parameters.mode == 'atom-centered': if self.d is None: dforces_dparameters = \ model.calculate_dForces_dParameters( self.fingerprints[hash], self.fingerprintprimes[hash]) else: dforces_dparameters = \ model.calculate_numerical_dForces_dParameters( self.fingerprints[hash], self.fingerprintprimes[hash], d=self.d) for selfindex in range(no_of_atoms): for i in range(3): temp = p.force_coefficient * (2.0 / 3.0) * \ (amp_forces[selfindex][i] - actual_forces[selfindex][i]) * \ dforces_dparameters[(selfindex, i)] \ / no_of_atoms dloss_dparameters += temp loss = p.energy_coefficient * energyloss if p.force_coefficient is not None: loss += p.force_coefficient * forceloss dloss_dparameters = np.array(dloss_dparameters) # if overfit coefficient is more than zero, overfit contribution to # loss and dloss_dparameters is also added. if p.overfit > 0.: overfitloss = 0. for component in parametervector: overfitloss += component ** 2. overfitloss *= p.overfit loss += overfitloss doverfitloss_dparameters = \ 2 * p.overfit * np.array(parametervector) dloss_dparameters += doverfitloss_dparameters return loss, dloss_dparameters, energyloss, forceloss, \ energy_maxresid, force_maxresid # All incoming requests will be dictionaries with three keys. # d['id']: process id number, assigned when process created above. # d['subject']: what the message is asking for / telling you. # d['data']: optional data passed from worker. def process_parallels(self, vector, server, n_pids, keys, args): """ Parameters ---------- vector : list Parameters of the regression model in the form of a list. server : object Master session of parallel processing. processes: list of objects Worker sessions for parallel processing. keys : list List of images keys for worker processes. args : dict Dictionary containing arguments of the method to be called on each worker process. """ # For each process finished = np.array([False] * n_pids) results = {'loss': 0., 'dloss_dparameters': [0.] * len(vector), 'energy_loss': 0., 'force_loss': 0., 'energy_maxresid': 0., 'force_maxresid': 0.} while not finished.all(): message = server.recv_pyobj() if message['subject'] == '': server.send_string('calculate_loss_function') elif message['subject'] == '': request = message['data'] # Variable name. if request == 'images': subimages = {k: self.images[k] for k in keys[int(message['id'])]} server.send_pyobj(subimages) elif request == 'fortran': server.send_pyobj(self._model.fortran) elif request == 'modelstring': server.send_pyobj(self._model.tostring()) elif request == 'lossfunctionstring': server.send_pyobj(self.parameters.tostring()) elif request == 'fingerprints': server.send_pyobj({k: self.fingerprints[k] for k in keys[int(message['id'])]}) elif request == 'fingerprintprimes': if self.fingerprintprimes is not None: server.send_pyobj({k: self.fingerprintprimes[k] for k in keys[int(message['id'])]}) else: server.send_pyobj(None) elif request == 'args': server.send_pyobj(args) elif request == 'parameters': if finished[int(message['id'])]: server.send_pyobj('') else: server.send_pyobj(vector) else: raise NotImplementedError() elif message['subject'] == '': result = message['data'] server.send_string('meaningless reply') results['loss'] += result['loss'] results['dloss_dparameters'] += result['dloss_dparameters'] results['energy_loss'] += result['energy_loss'] results['force_loss'] += result['force_loss'] if result['energy_maxresid'] > results['energy_maxresid']: results['energy_maxresid'] = result['energy_maxresid'] if result['force_maxresid'] > results['force_maxresid']: results['force_maxresid'] = result['force_maxresid'] finished[int(message['id'])] = True return results def check_convergence(self, loss, energy_loss, force_loss, energy_maxresid, force_maxresid): """Check convergence Checks to see whether convergence is met; if it is, raises ConvergenceException to stop the optimizer. Parameters ---------- loss : float Value of the loss function. energy_loss : float Value of the energy contribution of the loss function. force_loss : float Value of the force contribution of the loss function. energy_maxresid : float Maximum energy residual. force_maxresid : float Maximum force residual. """ p = self.parameters energy_rmse_converged = True log = self._model.log if p.convergence['energy_rmse'] is not None: energy_rmse = np.sqrt(energy_loss / len(self.images)) if energy_rmse > p.convergence['energy_rmse']: energy_rmse_converged = False energy_maxresid_converged = True if p.convergence['energy_maxresid'] is not None: if energy_maxresid > p.convergence['energy_maxresid']: energy_maxresid_converged = False if p.force_coefficient is not None: force_rmse_converged = True if p.convergence['force_rmse'] is not None: force_rmse = np.sqrt(force_loss / len(self.images)) if force_rmse > p.convergence['force_rmse']: force_rmse_converged = False force_maxresid_converged = True if p.convergence['force_maxresid'] is not None: if force_maxresid > p.convergence['force_maxresid']: force_maxresid_converged = False if self.log_losses: log('%5i %19s %12.4e %10.4e %1s' ' %10.4e %1s %10.4e %1s %10.4e %1s' % (self._step, now(), loss, energy_rmse, 'C' if energy_rmse_converged else '-', energy_maxresid, 'C' if energy_maxresid_converged else '-', force_rmse, 'C' if force_rmse_converged else '-', force_maxresid, 'C' if force_maxresid_converged else '-')) self._step += 1 return energy_rmse_converged and energy_maxresid_converged and \ force_rmse_converged and force_maxresid_converged else: if self.log_losses: log('%5i %19s %12.4e %10.4e %1s %10.4e %1s' % (self._step, now(), loss, energy_rmse, 'C' if energy_rmse_converged else '-', energy_maxresid, 'C' if energy_maxresid_converged else '-')) self._step += 1 return energy_rmse_converged and energy_maxresid_converged def calculate_fingerprints_range(fp, images): """Calculates the range for the fingerprints corresponding to images, stored in fp. fp is a fingerprints object with the fingerprints data stored in a dictionary-like object at fp.fingerprints. (Typically this is a .utilties.Data structure.) images is a hashed dictionary of atoms for which to consider the range. In image-centered mode, returns an array of (min, max) values for each fingerprint. In atom-centered mode, returns a dictionary of such arrays, one per element. """ if fp.parameters.mode == 'image-centered': raise NotImplementedError() elif fp.parameters.mode == 'atom-centered': fprange = {} for hash in images.keys(): imagefingerprints = fp.fingerprints[hash] for element, fingerprint in imagefingerprints: if element not in fprange: fprange[element] = [[_, _] for _ in fingerprint] else: assert len(fprange[element]) == len(fingerprint) for i, ridge in enumerate(fingerprint): if ridge < fprange[element][i][0]: fprange[element][i][0] = ridge elif ridge > fprange[element][i][1]: fprange[element][i][1] = ridge for key, value in fprange.items(): fprange[key] = value return fprange def ravel_data(train_forces, mode, images, fingerprints, fingerprintprimes,): """ Reshapes data of images into lists. Parameters --------- train_forces : bool Determining whether forces are also trained or not. mode : str Can be either 'atom-centered' or 'image-centered'. images : list or str List of ASE atoms objects with positions, symbols, energies, and forces in ASE format. This is the training set of data. This can also be the path to an ASE trajectory (.traj) or database (.db) file. Energies can be obtained from any reference, e.g. DFT calculations. fingerprints : dict Dictionary with images hashs as keys and the corresponding fingerprints as values. fingerprintprimes : dict Dictionary with images hashs as keys and the corresponding fingerprint derivatives as values. """ from ase.data import atomic_numbers actual_energies = [image.get_potential_energy(apply_constraint=False) for image in images.values()] if mode == 'atom-centered': num_images_atoms = [len(image) for image in images.values()] atomic_numbers = [atomic_numbers[atom.symbol] for image in images.values() for atom in image] def ravel_fingerprints(images, fingerprints): """ Reshape fingerprints of images into a list. """ raveled_fingerprints = [] elements = [] for hash, image in images.items(): for index in range(len(image)): elements += [fingerprints[hash][index][0]] raveled_fingerprints += [fingerprints[hash][index][1]] elements = sorted(set(elements)) # Could also work without images: # raveled_fingerprints = [afp # for hash, value in fingerprints.iteritems() # for (element, afp) in value] return elements, raveled_fingerprints elements, raveled_fingerprints = ravel_fingerprints(images, fingerprints) else: atomic_positions = [image.positions.ravel() for image in images.values()] if train_forces is True: actual_forces = \ [image.get_forces(apply_constraint=False)[index] for image in images.values() for index in range(len(image))] if mode == 'atom-centered': def ravel_neighborlists_and_fingerprintprimes(images, fingerprintprimes): """ Reshape neighborlists and fingerprintprimes of images into a list and a matrix, respectively. """ # Only neighboring atoms of type II (within the main cell) # need to be sent to fortran for force training. # All keys in fingerprintprimes are for type II neighborhoods. # Also note that each atom is considered as neighbor of # itself in fingerprintprimes. num_neighbors = [] raveled_neighborlists = [] raveled_fingerprintprimes = [] for hash, image in images.items(): for atom in image: selfindex = atom.index selfsymbol = atom.symbol selfneighborindices = [] selfneighborsymbols = [] for key, derafp in fingerprintprimes[hash].items(): # key = (selfindex, selfsymbol, nindex, nsymbol, i) # i runs from 0 to 2. neighbor indices and symbols # should be added just once. if key[0] == selfindex and key[4] == 0: selfneighborindices += [key[2]] selfneighborsymbols += [key[3]] neighborcount = 0 for nindex, nsymbol in zip(selfneighborindices, selfneighborsymbols): raveled_neighborlists += [nindex] neighborcount += 1 for i in range(3): fpprime = fingerprintprimes[hash][(selfindex, selfsymbol, nindex, nsymbol, i)] raveled_fingerprintprimes += [fpprime] num_neighbors += [neighborcount] return (num_neighbors, raveled_neighborlists, raveled_fingerprintprimes) (num_neighbors, raveled_neighborlists, raveled_fingerprintprimes) = \ ravel_neighborlists_and_fingerprintprimes(images, fingerprintprimes) if mode == 'image-centered': if not train_forces: return (actual_energies, atomic_positions) else: return (actual_energies, actual_forces, atomic_positions) else: if not train_forces: return (actual_energies, elements, num_images_atoms, atomic_numbers, raveled_fingerprints) else: return (actual_energies, actual_forces, elements, num_images_atoms, atomic_numbers, raveled_fingerprints, num_neighbors, raveled_neighborlists, raveled_fingerprintprimes) def send_data_to_fortran(_fmodules, energy_coefficient, force_coefficient, overfit, train_forces, num_atoms, num_images, actual_energies, actual_forces, atomic_positions, num_images_atoms, atomic_numbers, raveled_fingerprints, num_neighbors, raveled_neighborlists, raveled_fingerprintprimes, model, d): """ Function that sends images data to fortran code. Is used just once on each core. """ from ase.data import atomic_numbers as an if model.parameters.mode == 'image-centered': mode_signal = 1 elif model.parameters.mode == 'atom-centered': mode_signal = 2 _fmodules.images_props.num_images = num_images _fmodules.images_props.actual_energies = actual_energies if train_forces: _fmodules.images_props.actual_forces = actual_forces _fmodules.model_props.energy_coefficient = energy_coefficient _fmodules.model_props.force_coefficient = force_coefficient _fmodules.model_props.overfit = overfit _fmodules.model_props.train_forces = train_forces _fmodules.model_props.mode_signal = mode_signal if d is None: _fmodules.model_props.numericprime = False else: _fmodules.model_props.numericprime = True _fmodules.model_props.d = d if model.parameters.mode == 'atom-centered': fprange = model.parameters.fprange elements = sorted(fprange.keys()) num_elements = len(elements) elements_numbers = [an[elm] for elm in elements] min_fingerprints = \ [[fprange[elm][_][0] for _ in range(len(fprange[elm]))] for elm in elements] max_fingerprints = [[fprange[elm][_][1] for _ in range(len(fprange[elm]))] for elm in elements] num_fingerprints_of_elements = \ [len(fprange[elm]) for elm in elements] _fmodules.images_props.num_elements = num_elements _fmodules.images_props.elements_numbers = elements_numbers _fmodules.images_props.num_images_atoms = num_images_atoms _fmodules.images_props.atomic_numbers = atomic_numbers if train_forces: _fmodules.images_props.num_neighbors = num_neighbors _fmodules.images_props.raveled_neighborlists = \ raveled_neighborlists _fmodules.fingerprint_props.num_fingerprints_of_elements = \ num_fingerprints_of_elements _fmodules.fingerprint_props.raveled_fingerprints = raveled_fingerprints _fmodules.neuralnetwork.min_fingerprints = min_fingerprints _fmodules.neuralnetwork.max_fingerprints = max_fingerprints if train_forces: _fmodules.fingerprint_props.raveled_fingerprintprimes = \ raveled_fingerprintprimes else: _fmodules.images_props.num_atoms = num_atoms _fmodules.images_props.atomic_positions = atomic_positions # for neural neyworks only if model.parameters['importname'] == '.model.neuralnetwork.NeuralNetwork': hiddenlayers = model.parameters.hiddenlayers activation = model.parameters.activation if model.parameters.mode == 'atom-centered': from collections import OrderedDict no_layers_of_elements = \ [3 if isinstance(hiddenlayers[elm], int) else (len(hiddenlayers[elm]) + 2) for elm in elements] nn_structure = OrderedDict() for elm in elements: len_of_fps = len(fprange[elm]) if isinstance(hiddenlayers[elm], int): nn_structure[elm] = \ ([len_of_fps] + [hiddenlayers[elm]] + [1]) else: nn_structure[elm] = \ ([len_of_fps] + [layer for layer in hiddenlayers[elm]] + [1]) no_nodes_of_elements = [nn_structure[elm][_] for elm in elements for _ in range(len(nn_structure[elm]))] else: num_atoms = model.parameters.num_atoms if isinstance(hiddenlayers, int): no_layers_of_elements = [3] else: no_layers_of_elements = [len(hiddenlayers) + 2] if isinstance(hiddenlayers, int): nn_structure = ([3 * num_atoms] + [hiddenlayers] + [1]) else: nn_structure = ([3 * num_atoms] + [layer for layer in hiddenlayers] + [1]) no_nodes_of_elements = [nn_structure[_] for _ in range(len(nn_structure))] _fmodules.neuralnetwork.no_layers_of_elements = no_layers_of_elements _fmodules.neuralnetwork.no_nodes_of_elements = no_nodes_of_elements if activation == 'tanh': activation_signal = 1 elif activation == 'sigmoid': activation_signal = 2 elif activation == 'linear': activation_signal = 3 _fmodules.neuralnetwork.activation_signal = activation_signal amp-0.6/amp/model/__main__.py0000644000175000017500000001032413137634440015770 0ustar muammarmuammar"""Directly calling this module; apparently from another node. Calls should come as python -m amp.model id hostname:port This session will then start a zmq session with that socket, labeling itself with id. Instructions on what to do will come from the socket. """ import sys import tempfile import zmq from ..utilities import MessageDictionary, string2dict, Logger from .. import importhelper hostsocket = sys.argv[-1] proc_id = sys.argv[-2] msg = MessageDictionary(proc_id) # Send standard lines to stdout signaling process started and where # error is directed. print('') # Signal that program started. sys.stderr = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.stderr') print('Log and stderr written to %s' % sys.stderr.name) # Also send logger output to stderr to aid in debugging. log = Logger(file=sys.stderr) # Establish client session via zmq; find purpose. context = zmq.Context() socket = context.socket(zmq.REQ) socket.connect('tcp://%s' % hostsocket) socket.send_pyobj(msg('')) purpose = socket.recv_string() if purpose == 'calculate_loss_function': # Request variables. socket.send_pyobj(msg('', 'fortran')) fortran = socket.recv_pyobj() socket.send_pyobj(msg('', 'modelstring')) modelstring = socket.recv_pyobj() dictionary = string2dict(modelstring) Model = importhelper(dictionary.pop('importname')) log('Model received:') log(str(dictionary)) model = Model(fortran=fortran, **dictionary) model.log = log log('Model set up.') socket.send_pyobj(msg('', 'args')) args = socket.recv_pyobj() d = args['d'] socket.send_pyobj(msg('', 'lossfunctionstring')) lossfunctionstring = socket.recv_pyobj() dictionary = string2dict(lossfunctionstring) log(str(dictionary)) LossFunction = importhelper(dictionary.pop('importname')) lossfunction = LossFunction(parallel={'cores': 1}, raise_ConvergenceOccurred=False, d=d, **dictionary) log('Loss function set up.') images = None socket.send_pyobj(msg('', 'images')) images = socket.recv_pyobj() log('Images received.') fingerprints = None socket.send_pyobj(msg('', 'fingerprints')) fingerprints = socket.recv_pyobj() log('Fingerprints received.') fingerprintprimes = None socket.send_pyobj(msg('', 'fingerprintprimes')) fingerprintprimes = socket.recv_pyobj() log('Fingerprintprimes received.') # Set up local loss function. lossfunction.attach_model(model, fingerprints=fingerprints, fingerprintprimes=fingerprintprimes, images=images) log('Images, fingerprints, and fingerprintprimes ' 'attached to the loss function.') if model.fortran: log('fmodules will be used to evaluate loss function.') else: log('Fortran will not be used to evaluate loss function.') # Now wait for parameters, and send the component of the loss function. while True: socket.send_pyobj(msg('', 'parameters')) parameters = socket.recv_pyobj() if parameters == '': # FIXME/ap: I removed an fmodules.deallocate_variables() call # here. Do we need to add this to LossFunction? break elif parameters == '': # Master is waiting for other workers to finish. # Any more elegant way # to do this without opening another comm channel? # or having a thread for each process? pass else: # FIXME/ap: Why do we need to request this every time? # Couldn't it be part of earlier request? socket.send_pyobj(msg('', 'args')) args = socket.recv_pyobj() lossprime = args['lossprime'] output = lossfunction.get_loss(parameters, lossprime=lossprime) socket.send_pyobj(msg('', output)) socket.recv_string() else: raise NotImplementedError('Purpose "%s" unknown.' % purpose) amp-0.6/amp/model/neuralnetwork.py0000644000175000017500000013470613137634440017163 0ustar muammarmuammarimport os import numpy as np from collections import OrderedDict from ase.calculators.calculator import Parameters from . import LossFunction, calculate_fingerprints_range, Model from ..regression import Regressor from ..utilities import Logger, hash_images, make_filename class NeuralNetwork(Model): """Class that implements a basic feed-forward neural network. Parameters ---------- hiddenlayers : dict Dictionary of chemical element symbols and architectures of their corresponding hidden layers of the conventional neural network. Number of nodes of last layer is always one corresponding to energy. However, number of nodes of first layer is equal to three times number of atoms in the system in the case of no descriptor, and is equal to length of symmetry functions of the descriptor. Can be fed using tuples as: >>> hiddenlayers = (3, 2,) for example, in which a neural network with two hidden layers, the first one having three nodes and the second one having two nodes is assigned (to the whole atomic system in the no descriptor case, and to each chemical element in the atom-centered mode). When setting only one hidden layer, the dictionary can be fed as: >>> hiddenlayers = (3,) In the atom-centered mode, neural network for each species can be assigned seperately, as: >>> hiddenlayers = {"O":(3,5), "Au":(5,6)} for example. activation : str Assigns the type of activation funtion. "linear" refers to linear function, "tanh" refers to tanh function, and "sigmoid" refers to sigmoid function. weights : dict In the case of no descriptor, keys correspond to layers and values are two dimensional arrays of network weight. In the atom-centered mode, keys correspond to chemical elements and values are dictionaries with layer keys and network weight two dimensional arrays as values. Arrays are set up to connect node i in the previous layer with node j in the current layer with indices w[i,j]. The last value for index i corresponds to bias. If weights is not given, arrays will be randomly generated. scalings : dict In the case of no descriptor, keys are "intercept" and "slope" and values are real numbers. In the fingerprinting scheme, keys correspond to chemical elements and values are dictionaries with "intercept" and "slope" keys and real number values. If scalings is not given, it will be randomly generated. fprange : dict Range of fingerprints of each chemical species. Should be fed as a dictionary of chemical species and a list of minimum and maximun, e.g.: >>> fprange={"Pd": [0.31, 0.59], "O":[0.56, 0.72]} regressor : object Regressor object for finding best fit model parameters, e.g. by loss function optimization via amp.regression.Regressor. mode : str Can be either 'atom-centered' or 'image-centered'. lossfunction : object Loss function object, if at all desired by the user. version : object Version of this class. fortran : bool If True, allows for extrapolation, if False, does not allow. checkpoints : int Frequency with which to save parameter checkpoints upon training. E.g., 100 saves a checkpoint on each 100th training setp. Specify None for no checkpoints. Note: You can make this negative to not overwrite previous checkpoints. .. note:: Dimensions of weight two dimensional arrays should be consistent with hiddenlayers. Raises ------ RuntimeError, NotImplementedError """ def __init__(self, hiddenlayers=(5, 5), activation='tanh', weights=None, scalings=None, fprange=None, regressor=None, mode=None, lossfunction=None, version=None, fortran=True, checkpoints=100): # Version check, particularly if restarting. compatibleversions = ['2015.12', ] if (version is not None) and version not in compatibleversions: raise RuntimeError('Error: Trying to use NeuralNetwork' ' version %s, but this module only supports' ' versions %s. You may need an older or ' 'newer version of Amp.' % (version, compatibleversions)) else: version = compatibleversions[-1] # The parameters dictionary contains the minimum information # to produce a compatible model; e.g., one that gives # the identical energy (and/or forces) when fed a fingerprint. p = self.parameters = Parameters() p.importname = '.model.neuralnetwork.NeuralNetwork' p.version = version p.hiddenlayers = hiddenlayers p.weights = weights p.scalings = scalings p.fprange = fprange p.activation = activation p.mode = mode # Checking that the activation function is given correctly: if activation not in ['linear', 'tanh', 'sigmoid']: _ = ('Unknown activation function %s; must be one of ' '"linear", "tanh", or "sigmoid".' % activation) raise NotImplementedError(_) self.regressor = regressor self.parent = None # Can hold a reference to main Amp instance. self.lossfunction = lossfunction self.fortran = fortran self.checkpoints = checkpoints if self.lossfunction is None: self.lossfunction = LossFunction() def fit(self, trainingimages, descriptor, log, parallel, only_setup=False, ): """Fit the model parameters such that the fingerprints can be used to describe the energies in trainingimages. log is the logging object. descriptor is a descriptor object, as would be in calc.descriptor. Parameters ---------- trainingimages : dict Hashed dictionary of training images. descriptor : object Class representing local atomic environment. log : Logger object Write function at which to log data. Note this must be a callable function. parallel: dict Parallel configuration dictionary. Takes the same form as in amp.Amp. only_setup : bool only_setup is primarily for debugging. It initializes all variables but skips the last line of starting the regressor. """ # Set all parameters and report to logfile. self._parallel = parallel self._log = log if self.regressor is None: self.regressor = Regressor() p = self.parameters tp = self.trainingparameters = Parameters() tp.trainingimages = trainingimages tp.descriptor = descriptor if p.mode is None: p.mode = descriptor.parameters.mode else: assert p.mode == descriptor.parameters.mode log('Regression in %s mode.' % p.mode) if 'fprange' not in p or p.fprange is None: log('Calculating new fingerprint range; this range is part ' 'of the model.') p.fprange = calculate_fingerprints_range(descriptor, trainingimages) if p.mode == 'atom-centered': # If hiddenlayers is a tuple/list, convert to a dictionary. if not hasattr(p.hiddenlayers, 'keys'): p.hiddenlayers = {element: p.hiddenlayers for element in p.fprange.keys()} log('Hidden-layer structure:') if p.mode == 'image-centered': log(' %s' % str(p.hiddenlayers)) elif p.mode == 'atom-centered': for item in p.hiddenlayers.items(): log(' %2s: %s' % item) if p.weights is None: log('Initializing with random weights.') if p.mode == 'image-centered': raise NotImplementedError('Needs to be coded.') elif p.mode == 'atom-centered': p.weights = get_random_weights(p.hiddenlayers, p.activation, None, p.fprange) else: log('Initial weights already present.') if p.scalings is None: log('Initializing with random scalings.') if p.mode == 'image-centered': raise NotImplementedError('Need to code.') elif p.mode == 'atom-centered': p.scalings = get_random_scalings(trainingimages, p.activation, p.fprange.keys()) else: log('Initial scalings already present.') if only_setup: return # Regress the model. self.step = 0 result = self.regressor.regress(model=self, log=log) return result # True / False @property def forcetraining(self): """Returns true if forcetraining is turned on (as determined by examining the convergence criteria in the loss function), else returns False. """ if self.lossfunction.parameters['force_coefficient'] is None: forcetraining = False elif self.lossfunction.parameters['force_coefficient'] > 0.: forcetraining = True return forcetraining @property def vector(self): """Access to get or set the model parameters (weights, scaling for each network) as a single vector, useful in particular for regression. Parameters ---------- vector : list Parameters of the regression model in the form of a list. """ if self.parameters['weights'] is None: return None p = self.parameters if not hasattr(self, 'ravel'): self.ravel = Raveler(p.weights, p.scalings) return self.ravel.to_vector(weights=p.weights, scalings=p.scalings) @vector.setter def vector(self, vector): p = self.parameters if not hasattr(self, 'ravel'): self.ravel = Raveler(p.weights, p.scalings) weights, scalings = self.ravel.to_dicts(vector) p['weights'] = weights p['scalings'] = scalings def get_loss(self, vector): """Method to be called by the regression master. Takes one and only one input, a vector of parameters. Returns one output, the value of the loss (cost) function. Parameters ---------- vector : list Parameters of the regression model in the form of a list. """ if self.step == 0: filename = make_filename(self.parent.label, '-initial-parameters.amp') filename = self.parent.save(filename, overwrite=True) if self.checkpoints: if self.step % self.checkpoints == 0: self._log('Saving checkpoint data.') if self.checkpoints < 0: path = os.path.join(self.parent.label + '-checkpoints') if self.step == 0: if not os.path.exists(path): os.mkdir(path) filename = os.path.join(path, '{}.amp'.format(int(self.step))) else: filename = make_filename(self.parent.label, '-checkpoint.amp') self.parent.save(filename, overwrite=True) loss = self.lossfunction.get_loss(vector, lossprime=False)['loss'] if hasattr(self, 'observer'): self.observer(self, vector, loss) self.step += 1 return loss def get_lossprime(self, vector): """Method to be called by the regression master. Takes one and only one input, a vector of parameters. Returns one output, the value of the derivative of the loss function with respect to model parameters. Parameters ---------- vector : list Parameters of the regression model in the form of a list. """ return self.lossfunction.get_loss(vector, lossprime=True)['dloss_dparameters'] @property def lossfunction(self): """Allows the user to set a custom loss function. For example, >>> from amp.model import LossFunction >>> lossfxn = LossFunction(energy_tol=0.0001) >>> calc.model.lossfunction = lossfxn Parameters ---------- lossfunction : object Loss function object, if at all desired by the user. """ return self._lossfunction @lossfunction.setter def lossfunction(self, lossfunction): if hasattr(lossfunction, 'attach_model'): lossfunction.attach_model(self) # Allows access to methods. self._lossfunction = lossfunction def calculate_atomic_energy(self, afp, index, symbol,): """ Given input to the neural network, output (which corresponds to energy) is calculated about the specified atom. The sum of these for all atoms is the total energy (in atom-centered mode). Parameters --------- afp : list Atomic fingerprints in the form of a list to be used as input to the neural network. index: int Index of the atom for which atomic energy is calculated (only used in the atom-centered mode). symbol : str Symbol of the atom for which atomic energy is calculated (only used in the atom-centered mode). Returns ------- float Energy. """ if self.parameters.mode != 'atom-centered': raise AssertionError('calculate_atomic_energy should only be ' ' called in atom-centered mode.') scaling = self.parameters.scalings[symbol] outputs = calculate_nodal_outputs(self.parameters, afp, symbol,) atomic_amp_energy = scaling['slope'] * \ float(outputs[len(outputs) - 1]) + \ scaling['intercept'] return atomic_amp_energy def calculate_force(self, afp, derafp, direction, nindex=None, nsymbol=None,): """Given derivative of input to the neural network, derivative of output (which corresponds to forces) is calculated. Parameters ---------- afp : list Atomic fingerprints in the form of a list to be used as input to the neural network. derafp : list Derivatives of atomic fingerprints in the form of a list to be used as input to the neural network. direction : int Direction of force. nindex : int Index of the neighbor atom which force is acting at. (only used in the atom-centered mode) nsymbol : str Symbol of the neighbor atom which force is acting at. (only used in the atom-centered mode) Returns ------- float Force. """ scaling = self.parameters.scalings[nsymbol] outputs = calculate_nodal_outputs(self.parameters, afp, nsymbol,) dOutputs_dInputs = calculate_dOutputs_dInputs(self.parameters, derafp, outputs, nsymbol,) force = float((scaling['slope'] * dOutputs_dInputs[len(dOutputs_dInputs) - 1][0])) # force is multiplied by -1, because it is -dE/dx and not dE/dx. force *= -1. return force def calculate_dAtomicEnergy_dParameters(self, afp, index=None, symbol=None): """Returns the derivative of energy square error with respect to variables. Parameters ---------- afp : list Atomic fingerprints in the form of a list to be used as input to the neural network. index : int Index of the atom for which atomic energy is calculated (only used in the atom-centered mode) symbol : str Symbol of the atom for which atomic energy is calculated (only used in the atom-centered mode) Returns ------- list of float The value of the derivative of energy square error with respect to variables. """ p = self.parameters scaling = p.scalings[symbol] # self.W dictionary initiated. self.W = {} for elm in p.weights.keys(): self.W[elm] = {} weight = p.weights[elm] for _ in range(len(weight)): self.W[elm][_ + 1] = np.delete(weight[_ + 1], -1, 0) W = self.W[symbol] dAtomicEnergy_dParameters = np.zeros(self.ravel.count) dAtomicEnergy_dWeights, dAtomicEnergy_dScalings = \ self.ravel.to_dicts(dAtomicEnergy_dParameters) outputs = calculate_nodal_outputs(self.parameters, afp, symbol,) ohat, D, delta = calculate_ohat_D_delta(self.parameters, outputs, W) dAtomicEnergy_dScalings[symbol]['intercept'] = 1. dAtomicEnergy_dScalings[symbol][ 'slope'] = float(outputs[len(outputs) - 1]) for k in range(1, len(outputs)): dAtomicEnergy_dWeights[symbol][k] = float(scaling['slope']) * \ np.dot(np.matrix(ohat[k - 1]).T, np.matrix(delta[k]).T) dAtomicEnergy_dParameters = \ self.ravel.to_vector( dAtomicEnergy_dWeights, dAtomicEnergy_dScalings) return dAtomicEnergy_dParameters def calculate_dForce_dParameters(self, afp, derafp, direction, nindex=None, nsymbol=None,): """Returns the derivative of force square error with respect to variables. Parameters ---------- afp : list Atomic fingerprints in the form of a list to be used as input to the neural network. derafp : list Derivatives of atomic fingerprints in the form of a list to be used as input to the neural network. direction : int Direction of force. nindex : int Index of the neighbor atom which force is acting at. (only used in the atom-centered mode) nsymbol : str Symbol of the neighbor atom which force is acting at. (only used in the atom-centered mode) Returns ------- list of float The value of the derivative of force square error with respect to variables. """ p = self.parameters scaling = p.scalings[nsymbol] activation = p.activation # self.W dictionary initiated. self.W = {} for elm in p.weights.keys(): self.W[elm] = {} weight = p.weights[elm] for _ in range(len(weight)): self.W[elm][_ + 1] = np.delete(weight[_ + 1], -1, 0) W = self.W[nsymbol] dForce_dParameters = np.zeros(self.ravel.count) dForce_dWeights, dForce_dScalings = \ self.ravel.to_dicts(dForce_dParameters) outputs = calculate_nodal_outputs(self.parameters, afp, nsymbol,) ohat, D, delta = calculate_ohat_D_delta(self.parameters, outputs, W) dOutputs_dInputs = calculate_dOutputs_dInputs(self.parameters, derafp, outputs, nsymbol,) N = len(outputs) - 2 dD_dInputs = {} for k in range(1, N + 2): # Calculating coordinate derivative of D matrix dD_dInputs[k] = np.zeros(shape=(np.size(outputs[k]), np.size(outputs[k]))) for j in range(np.size(outputs[k])): if activation == 'linear': # linear dD_dInputs[k][j, j] = 0. elif activation == 'tanh': # tanh dD_dInputs[k][j, j] = \ - 2. * outputs[k][0, j] * dOutputs_dInputs[k][j] elif activation == 'sigmoid': # sigmoid dD_dInputs[k][j, j] = dOutputs_dInputs[k][j] - \ 2. * outputs[k][0, j] * dOutputs_dInputs[k][j] # Calculating coordinate derivative of delta dDelta_dInputs = {} # output layer dDelta_dInputs[N + 1] = dD_dInputs[N + 1] # hidden layers temp1 = {} temp2 = {} for k in range(N, 0, -1): temp1[k] = np.dot(W[k + 1], delta[k + 1]) temp2[k] = np.dot(W[k + 1], dDelta_dInputs[k + 1]) dDelta_dInputs[k] = \ np.dot(dD_dInputs[k], temp1[k]) + np.dot(D[k], temp2[k]) # Calculating coordinate derivative of ohat and # coordinates weights derivative of atomic_output dOhat_dInputs = {} dOutput_dInputsdWeights = {} for k in range(1, N + 2): dOhat_dInputs[k - 1] = [None] * (1 + len(dOutputs_dInputs[k - 1])) bound = len(dOutputs_dInputs[k - 1]) for count in range(bound): dOhat_dInputs[k - 1][count] = dOutputs_dInputs[k - 1][count] dOhat_dInputs[k - 1][count + 1] = 0. dOutput_dInputsdWeights[k] = \ np.dot(np.matrix(dOhat_dInputs[k - 1]).T, np.matrix(delta[k]).T) + \ np.dot(np.matrix(ohat[k - 1]).T, np.matrix(dDelta_dInputs[k]).T) for k in range(1, N + 2): dForce_dWeights[nsymbol][k] = float(scaling['slope']) * \ dOutput_dInputsdWeights[k] dForce_dScalings[nsymbol]['slope'] = dOutputs_dInputs[N + 1][0] dForce_dParameters = self.ravel.to_vector(dForce_dWeights, dForce_dScalings) # force is multiplied by -1, because it is -dE/dx and not dE/dx. dForce_dParameters *= -1. return dForce_dParameters # Auxiliary functions ######################################################### def calculate_nodal_outputs(parameters, afp, symbol,): """ Given input to the neural network, output (which corresponds to energy) is calculated about the specified atom. The sum of these for all atoms is the total energy (in atom-centered mode). Parameters ---------- parameters : dict ASE dictionary object. afp : list Atomic fingerprints in the form of a list to be used as input to the neural network. symbol : str Symbol of the atom for which atomic energy is calculated (only used in the atom-centered mode) Returns ------- dict Outputs of neural network nodes """ _afp = np.array(afp).copy() hiddenlayers = parameters.hiddenlayers[symbol] weight = parameters.weights[symbol] activation = parameters.activation fprange = parameters.fprange[symbol] # Scale the fingerprints to be in [-1, 1] range. for _ in range(np.shape(_afp)[0]): if (fprange[_][1] - fprange[_][0]) > (10.**(-8.)): _afp[_] = -1.0 + 2.0 * ((_afp[_] - fprange[_][0]) / (fprange[_][1] - fprange[_][0])) # Calculate node values. o = {} # node values layer = 1 # input layer net = {} # excitation ohat = {} # ohat is the nodal output matrix o concatenated by 1 for biases len_of_afp = len(_afp) # a temp variable is defined to construct the output matix o temp = np.zeros((1, len_of_afp + 1)) for _ in range(len_of_afp): temp[0, _] = _afp[_] temp[0, len(_afp)] = 1.0 ohat[0] = temp net[1] = np.dot(ohat[0], weight[1]) if activation == 'linear': o[1] = net[1] # linear activation elif activation == 'tanh': o[1] = np.tanh(net[1]) # tanh activation elif activation == 'sigmoid': # sigmoid activation o[1] = 1. / (1. + np.exp(-net[1])) temp = np.zeros((1, np.shape(o[1])[1] + 1)) bound = np.shape(o[1])[1] for _ in range(bound): temp[0, _] = o[1][0, _] temp[0, np.shape(o[1])[1]] = 1.0 ohat[1] = temp for hiddenlayer in hiddenlayers[1:]: layer += 1 net[layer] = np.dot(ohat[layer - 1], weight[layer]) if activation == 'linear': o[layer] = net[layer] # linear activation elif activation == 'tanh': o[layer] = np.tanh(net[layer]) # tanh activation elif activation == 'sigmoid': # sigmoid activation o[layer] = 1. / (1. + np.exp(-net[layer])) temp = np.zeros((1, np.size(o[layer]) + 1)) bound = np.size(o[layer]) for _ in range(bound): temp[0, _] = o[layer][0, _] temp[0, np.size(o[layer])] = 1.0 ohat[layer] = temp layer += 1 # output layer net[layer] = np.dot(ohat[layer - 1], weight[layer]) if activation == 'linear': o[layer] = net[layer] # linear activation elif activation == 'tanh': o[layer] = np.tanh(net[layer]) # tanh activation elif activation == 'sigmoid': # sigmoid activation o[layer] = 1. / (1. + np.exp(-net[layer])) del hiddenlayers, weight, ohat, net len_of_afp = len(_afp) temp = np.zeros((1, len_of_afp)) for _ in range(len_of_afp): temp[0, _] = _afp[_] o[0] = temp return o def calculate_dOutputs_dInputs(parameters, derafp, outputs, nsymbol,): """ Parameters ---------- parameters : dict ASE dictionary object. derafp : list Derivatives of atomic fingerprints in the form of a list to be used as input to the neural network. outputs : dict Outputs of neural network nodes. nsymbol : str Symbol of the atom for which atomic energy is calculated (only used in the atom-centered mode) Returns ------- dict Derivatives of outputs of neural network nodes w.r.t. inputs. """ _derafp = np.array(derafp).copy() hiddenlayers = parameters.hiddenlayers[nsymbol] weight = parameters.weights[nsymbol] activation = parameters.activation fprange = parameters.fprange[nsymbol] # Scaling derivative of fingerprints. for _ in range(len(_derafp)): if (fprange[_][1] - fprange[_][0]) > (10.**(-8.)): _derafp[_] = 2.0 * (_derafp[_] / (fprange[_][1] - fprange[_][0])) dOutputs_dInputs = {} # node values dOutputs_dInputs[0] = _derafp layer = 0 # input layer for hiddenlayer in hiddenlayers[0:]: layer += 1 temp = np.dot(np.matrix(dOutputs_dInputs[layer - 1]), np.delete(weight[layer], -1, 0)) dOutputs_dInputs[layer] = [None] * np.size(outputs[layer]) bound = np.size(outputs[layer]) for j in range(bound): if activation == 'linear': # linear function dOutputs_dInputs[layer][j] = float(temp[0, j]) elif activation == 'sigmoid': # sigmoid function dOutputs_dInputs[layer][j] = float(temp[0, j]) * \ float(outputs[layer][0, j] * (1. - outputs[layer][0, j])) elif activation == 'tanh': # tanh function dOutputs_dInputs[layer][j] = float(temp[0, j]) * \ float(1. - outputs[layer][0, j] * outputs[layer][0, j]) layer += 1 # output layer temp = np.dot(np.matrix(dOutputs_dInputs[layer - 1]), np.delete(weight[layer], -1, 0)) if activation == 'linear': # linear function dOutputs_dInputs[layer] = float(temp) elif activation == 'sigmoid': # sigmoid function dOutputs_dInputs[layer] = \ float(outputs[layer] * (1. - outputs[layer]) * temp) elif activation == 'tanh': # tanh function dOutputs_dInputs[layer] = \ float((1. - outputs[layer] * outputs[layer]) * temp) dOutputs_dInputs[layer] = [dOutputs_dInputs[layer]] return dOutputs_dInputs def calculate_ohat_D_delta(parameters, outputs, W): """Calculates extra matrices ohat, D, delta needed in mathematical manipulations. Notations are consistent with those of 'Rojas, R. Neural Networks - A Systematic Introduction. Springer-Verlag, Berlin, first edition 1996' Parameters ---------- parameters : dict ASE dictionary object. outputs : dict Outputs of neural network nodes. W : dict The same as weight dictionary, but the last rows associated with biases are deleted in W. """ activation = parameters.activation N = len(outputs) - 2 # number of hiddenlayers D = {} for k in range(N + 2): D[k] = np.zeros(shape=(np.size(outputs[k]), np.size(outputs[k]))) for j in range(np.size(outputs[k])): if activation == 'linear': # linear D[k][j, j] = 1. elif activation == 'sigmoid': # sigmoid D[k][j, j] = float(outputs[k][0, j]) * \ float((1. - outputs[k][0, j])) elif activation == 'tanh': # tanh D[k][j, j] = float(1. - outputs[k][0, j] * outputs[k][0, j]) # Calculating delta delta = {} # output layer delta[N + 1] = D[N + 1] # hidden layers for k in range(N, 0, -1): # backpropagate starting from output layer delta[k] = np.dot(D[k], np.dot(W[k + 1], delta[k + 1])) # Calculating ohat ohat = {} for k in range(1, N + 2): bound = np.size(outputs[k - 1]) ohat[k - 1] = np.zeros(shape=(1, bound + 1)) for j in range(bound): ohat[k - 1][0, j] = outputs[k - 1][0, j] ohat[k - 1][0, bound] = 1.0 return ohat, D, delta def get_random_weights(hiddenlayers, activation, no_of_atoms=None, fprange=None): """Generates random weight arrays from variables. hiddenlayers: dict Dictionary of chemical element symbols and architectures of their corresponding hidden layers of the conventional neural network. Number of nodes of last layer is always one corresponding to energy. However, number of nodes of first layer is equal to three times number of atoms in the system in the case of no descriptor, and is equal to length of symmetry functions in the atom-centered mode. Can be fed as: >>> hiddenlayers = (3, 2,) for example, in which a neural network with two hidden layers, the first one having three nodes and the second one having two nodes is assigned (to the whole atomic system in the case of no descriptor, and to each chemical element in the atom-centered mode). In the atom-centered mode, neural network for each species can be assigned seperately, as: >>> hiddenlayers = {"O":(3,5), "Au":(5,6)} for example. activation : str Assigns the type of activation funtion. "linear" refers to linear function, "tanh" refers to tanh function, and "sigmoid" refers to sigmoid function. no_of_atoms : int Number of atoms in atomic systems; used only in the case of no descriptor. fprange : dict Range of fingerprints of each chemical species. Should be fed as a dictionary of chemical species and a list of minimum and maximun, e.g: >>> fprange={"Pd": [0.31, 0.59], "O":[0.56, 0.72]} Returns ------- float weights """ weight = {} nn_structure = {} if no_of_atoms is not None: # pure atomic-coordinates scheme if isinstance(hiddenlayers, int): nn_structure = ([3 * no_of_atoms] + [hiddenlayers] + [1]) else: nn_structure = ( [3 * no_of_atoms] + [layer for layer in hiddenlayers] + [1]) weight = {} # Instead try Andrew Ng coursera approach. +/- epsilon # epsilon = sqrt(6./(n_i + n_o)) # where the n's are the number of input and output nodes. # Note: need to double that here with the math below. epsilon = np.sqrt(6. / (nn_structure[0] + nn_structure[1])) normalized_arg_range = 2. * epsilon weight[1] = np.random.random((3 * no_of_atoms + 1, nn_structure[1])) * \ normalized_arg_range - \ normalized_arg_range / 2. len_of_hiddenlayers = len(list(nn_structure)) - 3 for layer in range(len_of_hiddenlayers): epsilon = np.sqrt(6. / (nn_structure[layer + 1] + nn_structure[layer + 2])) normalized_arg_range = 2. * epsilon weight[layer + 2] = np.random.random( (nn_structure[layer + 1] + 1, nn_structure[layer + 2])) * \ normalized_arg_range - normalized_arg_range / 2. epsilon = np.sqrt(6. / (nn_structure[-2] + nn_structure[-1])) normalized_arg_range = 2. * epsilon weight[len(list(nn_structure)) - 1] = \ np.random.random((nn_structure[-2] + 1, 1)) \ * normalized_arg_range - normalized_arg_range / 2. if False: # This seemed to be setting all biases to zero? len_of_weight = len(weight) for _ in range(len_of_weight): # biases size = weight[_ + 1][-1].size for __ in range(size): weight[_ + 1][-1][__] = 0. else: elements = fprange.keys() for element in sorted(elements): len_of_fps = len(fprange[element]) if isinstance(hiddenlayers[element], int): nn_structure[element] = ([len_of_fps] + [hiddenlayers[element]] + [1]) else: nn_structure[element] = ( [len_of_fps] + [layer for layer in hiddenlayers[element]] + [1]) weight[element] = {} # Instead try Andrew Ng coursera approach. +/- epsilon # epsilon = sqrt(6./(n_i + n_o)) # where the n's are the number of input and output nodes. # Note: need to double that here with the math below. epsilon = np.sqrt(6. / (nn_structure[element][0] + nn_structure[element][1])) normalized_arg_range = 2. * epsilon weight[element][1] = np.random.random((len(fprange[element]) + 1, nn_structure[ element][1])) * \ normalized_arg_range - \ normalized_arg_range / 2. len_of_hiddenlayers = len(list(nn_structure[element])) - 3 for layer in range(len_of_hiddenlayers): epsilon = np.sqrt(6. / (nn_structure[element][layer + 1] + nn_structure[element][layer + 2])) normalized_arg_range = 2. * epsilon weight[element][layer + 2] = np.random.random( (nn_structure[element][layer + 1] + 1, nn_structure[element][layer + 2])) * \ normalized_arg_range - normalized_arg_range / 2. epsilon = np.sqrt(6. / (nn_structure[element][-2] + nn_structure[element][-1])) normalized_arg_range = 2. * epsilon weight[element][len(list(nn_structure[element])) - 1] = \ np.random.random((nn_structure[element][-2] + 1, 1)) \ * normalized_arg_range - normalized_arg_range / 2. if False: # This seemed to be setting all biases to zero? len_of_weight = len(weight[element]) for _ in range(len_of_weight): # biases size = weight[element][_ + 1][-1].size for __ in range(size): weight[element][_ + 1][-1][__] = 0. return weight def get_random_scalings(images, activation, elements=None): """Generates initial scaling matrices, such that the range of activation is scaled to the range of actual energies. images : dict ASE atoms objects (the training set). activation: str Assigns the type of activation funtion. "linear" refers to linear function, "tanh" refers to tanh function, and "sigmoid" refers to sigmoid function. elements: list of str List of atom symbols; used in the atom-centered mode only. Returns ------- float scalings """ hashs = list(images.keys()) no_of_images = len(hashs) max_act_energy = max(image.get_potential_energy(apply_constraint=False) for image in images.values()) min_act_energy = min(image.get_potential_energy(apply_constraint=False) for image in images.values()) for count in range(no_of_images): hash = hashs[count] image = images[hash] no_of_atoms = len(image) if image.get_potential_energy(apply_constraint=False) == \ max_act_energy: no_atoms_of_max_act_energy = no_of_atoms if image.get_potential_energy(apply_constraint=False) == \ min_act_energy: no_atoms_of_min_act_energy = no_of_atoms max_act_energy_per_atom = max_act_energy / no_atoms_of_max_act_energy min_act_energy_per_atom = min_act_energy / no_atoms_of_min_act_energy scaling = {} if elements is None: # pure atomic-coordinates scheme scaling = {} if activation == 'sigmoid': # sigmoid activation function scaling['intercept'] = min_act_energy_per_atom scaling['slope'] = (max_act_energy_per_atom - min_act_energy_per_atom) elif activation == 'tanh': # tanh activation function scaling['intercept'] = (max_act_energy_per_atom + min_act_energy_per_atom) / 2. scaling['slope'] = (max_act_energy_per_atom - min_act_energy_per_atom) / 2. elif activation == 'linear': # linear activation function scaling['intercept'] = (max_act_energy_per_atom + min_act_energy_per_atom) / 2. scaling['slope'] = (10. ** (-10.)) * \ (max_act_energy_per_atom - min_act_energy_per_atom) / 2. else: # atom-centered mode for element in elements: scaling[element] = {} if activation == 'sigmoid': # sigmoid activation function scaling[element]['intercept'] = min_act_energy_per_atom scaling[element]['slope'] = (max_act_energy_per_atom - min_act_energy_per_atom) elif activation == 'tanh': # tanh activation function scaling[element]['intercept'] = (max_act_energy_per_atom + min_act_energy_per_atom) / 2. scaling[element]['slope'] = (max_act_energy_per_atom - min_act_energy_per_atom) / 2. elif activation == 'linear': # linear activation function scaling[element]['intercept'] = (max_act_energy_per_atom + min_act_energy_per_atom) / 2. scaling[element]['slope'] = (10. ** (-10.)) * \ (max_act_energy_per_atom - min_act_energy_per_atom) / 2. return scaling class Raveler: """Class to ravel and unravel variable values into a single vector. This is used for feeding into the optimizer. Feed in a list of dictionaries to initialize the shape of the transformation. Note no data is saved in the class; each time it is used it is passed either the dictionaries or vector. The dictionaries for initialization should be two levels deep. weights, scalings are the variables to ravel and unravel """ def __init__(self, weights, scalings): self.count = 0 self.weightskeys = [] self.scalingskeys = [] for key1 in sorted(weights.keys()): # element for key2 in sorted(weights[key1].keys()): # layer value = weights[key1][key2] self.weightskeys.append({'key1': key1, 'key2': key2, 'shape': np.array(value).shape, 'size': np.array(value).size}) self.count += np.array(weights[key1][key2]).size for key1 in sorted(scalings.keys()): # element for key2 in sorted(scalings[key1].keys()): # slope / intercept self.scalingskeys.append({'key1': key1, 'key2': key2}) self.count += 1 self.vector = np.zeros(self.count) def to_vector(self, weights, scalings): """Puts the weights and scalings embedded dictionaries into a single vector and returns it. The dictionaries need to have the identical structure to those it was initialized with.""" vector = np.zeros(self.count) count = 0 for k in self.weightskeys: lweights = np.array(weights[k['key1']][k['key2']]).ravel() vector[count:(count + lweights.size)] = lweights count += lweights.size for k in self.scalingskeys: vector[count] = scalings[k['key1']][k['key2']] count += 1 return vector def to_dicts(self, vector): """Puts the vector back into weights and scalings dictionaries of the form initialized. vector must have same length as the output of unravel.""" assert len(vector) == self.count count = 0 weights = OrderedDict() scalings = OrderedDict() for k in self.weightskeys: if k['key1'] not in weights.keys(): weights[k['key1']] = OrderedDict() matrix = vector[count:count + k['size']] matrix = matrix.flatten() matrix = np.matrix(matrix.reshape(k['shape'])) weights[k['key1']][k['key2']] = matrix.tolist() count += k['size'] for k in self.scalingskeys: if k['key1'] not in scalings.keys(): scalings[k['key1']] = OrderedDict() scalings[k['key1']][k['key2']] = vector[count] count += 1 return weights, scalings # Analysis tools ############################################################## class NodePlot: """Creates plots to visualize the output of the nodes in the neural networks. initialize with a calculator that has parameters; e.g. a trained calculator or else one in which fit has been called with the setup_only flag turned on. Call with the 'plot' method, which takes as argment a list of images """ def __init__(self, calc): self.calc = calc self.data = {} # For accumulating the data. # Local imports; these are not package-wide dependencies. from matplotlib import pyplot from matplotlib.backends.backend_pdf import PdfPages self.pyplot = pyplot self.PdfPages = PdfPages def plot(self, images, filename='nodeplot.pdf'): """ Creates a plot of the output of each node, as a violin plot. """ calc = self.calc log = Logger('develop.log') images = hash_images(images, log=log) calc.descriptor.calculate_fingerprints(images=images, parallel={'cores': 1}, log=log, calculate_derivatives=False) for hash in images.keys(): fingerprints = calc.descriptor.fingerprints[hash] for fp in fingerprints: outputs = calculate_nodal_outputs(calc.model.parameters, afp=fp[1], symbol=fp[0]) self._accumulate(symbol=fp[0], output=outputs) self._finalize_table() with self.PdfPages(filename) as pdf: for symbol in self.data.keys(): fig = self._makefig(symbol) pdf.savefig(fig) self.pyplot.close(fig) def _makefig(self, symbol, save=False): """Makes a figure for one element.""" fig = self.pyplot.figure(figsize=(8.5, 11.0)) lm = 0.1 rm = 0.05 bm = 0.05 tm = 0.05 vg = 0.05 numplots = 1 + self.data[symbol]['header'][-1][0] axwidth = 1. - lm - rm axheight = (1. - bm - tm - (numplots - 1) * vg) / numplots d = self.data[symbol] for layer in range(1 + d['header'][-1][0]): ax = fig.add_axes((lm, 1. - tm - axheight - (axheight + vg) * layer, axwidth, axheight)) indices = [_ for _, label in enumerate(d['header']) if label[0] == layer] sub = d['table'][:, indices] ax.violinplot(dataset=sub, positions=range(len(indices))) ax.set_ylim(-1.2, 1.2) ax.set_xlim(-0.5, len(indices) - 0.5) ax.set_ylabel('Layer %i' % layer) ax.set_xlabel('node') fig.text(0.5, 1. - 0.5 * tm, 'Node outputs for %s' % symbol, ha='center', va='center') if save: fig.savefig(save) return fig def _accumulate(self, symbol, output): """Accumulates the data for the symbol.""" data = self.data layerkeys = list(output.keys()) # Correspond to layers. if symbol not in data: # Create headers, structure. data[symbol] = {'header': [], 'table': []} for layerkey in layerkeys: v = output[layerkey] v = v.reshape(v.size).tolist() data[symbol]['header'].extend([(layerkey, _) for _ in range(len(v))]) # Add as a row to data table. row = [] for layerkey in layerkeys: v = output[layerkey] v = v.reshape(v.size).tolist() row.extend(v) data[symbol]['table'].append(row) def _finalize_table(self): """Converts the data table into a numpy array.""" for symbol in self.data: self.data[symbol]['table'] = np.array(self.data[symbol]['table']) amp-0.6/amp/model/tflow.py0000644000175000017500000023565313137634440015421 0ustar muammarmuammar# This module was contributed by: # Zachary Ulissi # Department of Chemical Engineering # Stanford University # zulissi@gmail.com # Help/testing/discussions: Andrew Doyle (Stanford) and # the AMP development team # This module implements energy- and force- training using Google's # TensorFlow library. In doing so, the training is multithreaded and GPU # accelerated. import numpy as np import uuid from . import LossFunction from ..utilities import ConvergenceOccurred try: import tensorflow as tf from tensorflow.contrib.opt import ScipyOptimizerInterface except ImportError: # A warning is raised instead of an error so that documentation can # build without tensorflow installed. import warnings warnings.warn('Please install tensorflow if you plan to use this ' 'Amp module.') class NeuralNetwork: """TensorFlow-based Neural Network model. Uses Google's machine-learning code to construct a neural network. This method also allows for GPU acceleration. Parameters ---------- hiddenlayers Structure of the neural network. Can either be in the format (int,int,int), where each element represnts the size of a layer and there and the length of the list is the number of layers, or dictionary format of the network structure for each element type. E.g. {'Cu': (5, 5), 'O': (10, 5)} activation Activation type. (XXX Provide list of possibilities.) keep_prob : float Dropout rate for the neural network to reduce overfitting. (keep_prob=1. uses all nodes, keep_prob~0.5-0.8 better for training) maxTrainingEpochs : int Maximum number of times to loop through the training data before giving up. batchsize : int Batch size for minibatch (if miniBatch is set to True). initialTrainingRate Initial training rate for SGD optimizers like ADAM. See the TF documentation for choose this value. Likely between 1e-2 and 1e-5, depending on use case, whether mini-batch is on, etc. miniBatch : bool Whether to use minibatches in training. tfVars Tensorflow variables (used if restoring from a previous save). saveVariableName : str Name used for the internal tensorflow variable naming scheme. If variables have the same name as another model in the same tensorflow session, there will be collisions. parameters Dictionary of parameters to be used in initialization. Mostly these are the same keywords as the keyword arguments in this function. This is primarily used to make saving/loading easier. sess tensorflow session to use (None means start a new session) maxAtomsForces : int Number of atoms to be used in the force training. It sets the upper bound on the number of atoms that can be used to calculate the force for. E.g., if maxAtomsForces=40, then forces can only be calculated for images with less than 40 atoms. energy_coefficient : float Used to adjust the loss function; this is the weight applied to the energy component. force_coefficient : float or None Used to adjust the loss function; this is the weight applied to the force component. Note you can turn off force training by setting this to None. convergenceCriteria: dict Dictionary of convergence criteria, analagous to the main AMP convergence criteria dictionary. optimizationMethod: string Set the optimization method for the NN parameters. Currently either 'ADAM' for the ADAM optimizer in tensorflow, of 'l-BFGS-b' for the deterministic l-BFGS-b method. ADAM is usually faster per training step, has all of the benefits of being a stochastic optimizer, and allows for mini-batch operation, but has more tunable parameters and can be harder to get working well. l-BFGS-b usually works for small/moderate network sizes. input_keep_prob Dropout ratio on the first layer (from fingerprints to the neural network. Rule of thumb is this should be 0 to 0.2. Only applies when using a SGD optimizer like ADAM. BFGS ignores this. ADAM_optimizer_params Dictionary of parameters to pass to the ADAM optimizer. See https://www.tensorflow.org/versions/r0.11/api_docs/python/ train.html#AdamOptimizer for documentation regularization_strength Weight for L2-regularization in the cost function fprange: dict This is a dictionary that contains the minimum and maximum values seen for each fingerprint of each element. These weights: np array Input that allows the NN weights (and biases) to be set directly. This is only used for verifying that the calculation is working correctly in the CuOPd test case. In general, don't use this except for testing the code. This argument is analagous to the original AMP NeuralNetwork module. scalings Input that allows the NN final scaling o be set directly. This is only used for verifying that the calculation is working correctly in the CuOPd test case. In general, don't use this except for testing the code. This argument is analagous to the original AMP NeuralNetwork module. unit_type: string Sets the internal datatype of the tensorflow model. Either "float" for 32-bit FP precision, or "double" for 64-bit FP precision. preLoadTrainingData: bool Decides whether to run the training by preloading all training data into tensorflow. Doing so results in faster training if the entire dataset can fit into memory. This only works when not using mini-batch. relativeForceCutoff: float Parameter for controlling whether the force contribution to the trained cost function is absolute (just differences of force compared to training forces) or relative for large values of the force. This basically sets the upper limit on the forces that should be fitted (e.g. if the force is >A, then the force is scaled). This helps when a small number of images have very large forces that don't need to be reconstructed perfectly. """ def __init__(self, hiddenlayers=(5, 5), activation='tanh', keep_prob=1., maxTrainingEpochs=10000, importname=None, batchsize=2, initialTrainingRate=1e-4, miniBatch=False, tfVars=None, saveVariableName=None, parameters=None, sess=None, energy_coefficient=1.0, force_coefficient=0.04, scikit_model=None, convergenceCriteria=None, optimizationMethod='l-BFGS-b', input_keep_prob=0.8, ADAM_optimizer_params={'beta1': 0.9}, regularization_strength=None, numTrainingImages={}, elementFingerprintLengths=None, fprange=None, weights=None, scalings=None, unit_type="float", preLoadTrainingData=True, relativeForceCutoff=None ): self.parameters = {} if parameters is None else parameters for prop in ['energyMeanScale', 'energyPerElement']: if prop not in self.parameters: self.parameters[prop] = 0. for prop in ['energyProdScale']: if prop not in self.parameters: self.parameters[prop] = 1. if 'convergence' in self.parameters: 1 elif convergenceCriteria is None: self.parameters['convergence'] = {'energy_rmse': 0.001, 'energy_maxresid': None, 'force_rmse': 0.005, 'force_maxresid': None} else: self.parameters['convergence'] = convergenceCriteria if 'energy_coefficient' not in self.parameters: self.parameters['energy_coefficient'] = energy_coefficient if 'force_coefficient' not in self.parameters: self.parameters['force_coefficient'] = force_coefficient if 'ADAM_optimizer_params' not in self.parameters: self.parameters['ADAM_optimizer_params'] = ADAM_optimizer_params if 'regularization_strength' not in self.parameters: self.parameters['regularization_strength'] =\ regularization_strength if 'relativeForceCutoff' not in self.parameters: self.parameters['relativeForceCutoff'] = relativeForceCutoff if 'unit_type' not in self.parameters: self.parameters['unit_type'] = unit_type if 'preLoadTrainingData' not in self.parameters: self.parameters['preLoadTrainingData'] = preLoadTrainingData if 'fprange' not in self.parameters and fprange is not None: self.parameters['fprange'] = {} for element in fprange: _ = np.array([map(lambda x: x[0], fprange[element]), map(lambda x: x[1], fprange[element])]) self.parameters['fprange'][element] = _ self.hiddenlayers = hiddenlayers if isinstance(activation, basestring): self.activationName = activation self.activation = eval('tf.nn.' + activation) else: self.activation = activation self.activationName = activation.__name__ self.keep_prob = keep_prob self.input_keep_prob = input_keep_prob if saveVariableName is None: self.saveVariableName = str(uuid.uuid4())[:8] else: self.saveVariableName = saveVariableName if elementFingerprintLengths is not None: self.elements = elementFingerprintLengths.keys() self.elements.sort() self.elementFingerprintLengths = {} for element in self.elements: self.elementFingerprintLengths[element] =\ elementFingerprintLengths[element] self.weights = weights self.scalings = scalings self.sess = sess self.graph = None if tfVars is not None: self.constructSessGraphModel(tfVars, self.sess) if weights is not None: self.elementFingerprintLengths = {} self.elements = weights.keys() for element in self.elements: self.elementFingerprintLengths[element] =\ weights[element][1].shape[0] - 1 self.constructSessGraphModel(tfVars, self.sess) self.tfVars = tfVars self.maxTrainingEpochs = maxTrainingEpochs self.importname = '.model.neuralnetwork.tflow' self.batchsize = batchsize self.initialTrainingRate = initialTrainingRate self.miniBatch = miniBatch # Optimizer can be 'ADAM' or 'l-BFGS-b'. self.optimizationMethod = optimizationMethod # self.forcetraining is queried by the main Amp instance. if self.parameters['force_coefficient'] is None: self.forcetraining = False self.parameters['convergence']['force_rmse'] = None self.parameters['convergence']['force_maxresid'] = None else: self.forcetraining = True def constructSessGraphModel(self, tfVars, sess, trainOnly=False, numElements=None, numTrainingImages=None, num_dgdx_Eindices=None, numTrainingAtoms=None): self.graph = tf.Graph() with self.graph.as_default(): if sess is None: self.sess = tf.InteractiveSession() else: self.sess = sess if trainOnly: self.constructModel(self.sess, self.graph, trainOnly, numElements, numTrainingImages, num_dgdx_Eindices, numTrainingAtoms) else: self.constructModel(self.sess, self.graph) trainvarlist = tf.trainable_variables() trainvarlist = [a for a in trainvarlist if a.name[:8] == self.saveVariableName] self.saver = tf.train.Saver(trainvarlist) if tfVars is not None: self.sess.run(tf.initialize_all_variables()) with open('tfAmpNN-checkpoint-restore', 'w') as fhandle: fhandle.write(tfVars) self.saver.restore(self.sess, 'tfAmpNN-checkpoint-restore') else: self.sess.run(tf.initialize_all_variables()) # This function is used to test the code by pre-setting the weights in the # model for each element, so that results can be checked against # pre-computed exact estimates def setWeightsScalings(self, feedinput, weights, scalings): with self.graph.as_default(): namefun = lambda x: '%s_%s_' % (self.saveVariableName, element) + x for element in weights: for layer in weights[element]: weight = weights[element][layer][0:-1] bias = weights[element][layer][-1] bias = np.array(bias).reshape(bias.size) feedinput[self.graph.get_tensor_by_name( namefun('Wfc%d:0' % (layer - 1)))] = weight feedinput[self.graph.get_tensor_by_name( namefun('bfc%d:0' % (layer - 1)))] = bias feedinput[ self.graph.get_tensor_by_name(namefun('Wfcout:0'))] = \ np.array(scalings[element]['slope']).reshape((1, 1)) feedinput[ self.graph.get_tensor_by_name(namefun('bfcout:0'))] = \ np.array(scalings[element]['intercept']).reshape((1,)) def constructModel(self, sess, graph, preLoadData=False, numElements=None, numTrainingImages=None, num_dgdx_Eindices=None, numTrainingAtoms=None): """Sets up the tensorflow neural networks for each atom type.""" with sess.as_default(), graph.as_default(): # Make tensorflow inputs for each element. tensordict = {} indsdict = {} maskdict = {} dgdx_dict = {} dgdx_Eindices_dict = {} dgdx_Xindices_dict = {} if preLoadData: tensordictInitializer = {} dgdx_dict_initializer = {} dgdx_Eindices_dict_initializer = {} dgdx_Xindices_dict_initializer = {} indsdictInitializer = {} maskdictInitializer = {} for element in self.elements: if preLoadData: tensordictInitializer[element] = \ tf.placeholder(self.parameters['unit_type'], shape=[numElements[element], self.elementFingerprintLengths[ element]], name='tensor_%s' % element,) dgdx_dict_initializer[element] = \ tf.placeholder(self.parameters['unit_type'], shape=[num_dgdx_Eindices[element], self.elementFingerprintLengths[ element], 3], name='dgdx_%s' % element,) dgdx_Eindices_dict_initializer[element] = \ tf.placeholder("int64", shape=[num_dgdx_Eindices[element]], name='dgdx_Eindices_%s' % element,) dgdx_Xindices_dict_initializer[element] = \ tf.placeholder("int64", shape=[num_dgdx_Eindices[element]], name='dgdx_Xindices_%s' % element,) indsdictInitializer[element] = \ tf.placeholder("int64", shape=[numElements[element]], name='indsdict_%s' % element,) maskdictInitializer[element] = \ tf.placeholder(self.parameters['unit_type'], shape=[numTrainingImages, 1], name='maskdict_%s' % element,) tensordict[element] = \ tf.Variable(tensordictInitializer[element], trainable=False, collections=[],) dgdx_dict[element] = \ tf.Variable(dgdx_dict_initializer[element], trainable=False, collections=[],) dgdx_Eindices_dict[element] = \ tf.Variable(dgdx_Eindices_dict_initializer[element], trainable=False, collections=[],) dgdx_Xindices_dict[element] = \ tf.Variable(dgdx_Xindices_dict_initializer[element], trainable=False, collections=[]) indsdict[element] = \ tf.Variable(indsdictInitializer[element], trainable=False, collections=[]) maskdict[element] = \ tf.Variable(maskdictInitializer[element], trainable=False, collections=[]) else: tensordict[element] = \ tf.placeholder(self.parameters['unit_type'], shape=[None, self.elementFingerprintLengths[ element]], name='tensor_%s' % element,) dgdx_dict[element] = \ tf.placeholder(self.parameters['unit_type'], shape=[None, self.elementFingerprintLengths[ element], 3], name='dgdx_%s' % element) dgdx_Eindices_dict[element] = \ tf.placeholder("int64", shape=[None], name='dgdx_Eindices_%s' % element) dgdx_Xindices_dict[element] = \ tf.placeholder("int64", shape=[None], name='dgdx_Xindices_%s' % element) indsdict[element] = \ tf.placeholder("int64", shape=[None], name='indsdict_%s' % element) maskdict[element] = \ tf.placeholder(self.parameters['unit_type'], shape=[None, 1], name='maskdict_%s' % element) self.indsdict = indsdict self.tensordict = tensordict self.maskdict = maskdict self.dgdx_dict = dgdx_dict self.dgdx_Eindices_dict = dgdx_Eindices_dict self.dgdx_Xindices_dict = dgdx_Xindices_dict # y_ is the input energy for each configuration. if preLoadData: y_Initializer = \ tf.placeholder(self.parameters['unit_type'], shape=[numTrainingImages, 1], name='y_') input_keep_prob_inInitializer = \ tf.placeholder(self.parameters['unit_type'], shape=[], name='input_keep_prob_in') keep_prob_inInitializer = \ tf.placeholder(self.parameters['unit_type'], shape=[], name='keep_prob_in') nAtoms_inInitializer = \ tf.placeholder(self.parameters['unit_type'], shape=[numTrainingImages, 1], name='nAtoms_in') nAtoms_forces_Initializer = \ tf.placeholder(self.parameters['unit_type'], shape=[numTrainingAtoms, 1], name='nAtoms_forces') batchsizeInputInitializer = \ tf.placeholder("int32", shape=[], name='batchsizeInput') learningrateInitializer = \ tf.placeholder(self.parameters['unit_type'], shape=[], name='learningrate') forces_inInitializer = \ tf.placeholder(self.parameters['unit_type'], shape=[numTrainingAtoms, 3], name='forces_in') energycoefficientInitializer = \ tf.placeholder(self.parameters['unit_type'], shape=[]) forcecoefficientInitializer = \ tf.placeholder(self.parameters['unit_type'], shape=[]) energyProdScaleInitializer = \ tf.placeholder(self.parameters['unit_type'], shape=[], name='energyProdScale') totalNumAtomsInitializer = \ tf.placeholder("int32", shape=[], name='totalNumAtoms') self.y_ = \ tf.Variable(y_Initializer, trainable=False, collections=[]) self.input_keep_prob_in = \ tf.Variable(input_keep_prob_inInitializer, trainable=False, collections=[]) self.keep_prob_in = \ tf.Variable(keep_prob_inInitializer, trainable=False, collections=[]) self.nAtoms_in = \ tf.Variable(nAtoms_inInitializer, trainable=False, collections=[]) self.batchsizeInput = \ tf.Variable(batchsizeInputInitializer, trainable=False, collections=[]) self.learningrate = \ tf.Variable(learningrateInitializer, trainable=False, collections=[]) self.forces_in = \ tf.Variable(forces_inInitializer, trainable=False, collections=[]) self.energycoefficient = \ tf.Variable(energycoefficientInitializer, trainable=False, collections=[]) self.forcecoefficient = \ tf.Variable(forcecoefficientInitializer, trainable=False, collections=[]) self.energyProdScale = \ tf.Variable(energyProdScaleInitializer, trainable=False, collections=[]) self.totalNumAtoms = \ tf.Variable(totalNumAtomsInitializer, trainable=False, collections=[]) self.nAtoms_forces = \ tf.Variable(nAtoms_forces_Initializer, trainable=False, collections=[]) self.initializers = \ {'indsdict': indsdictInitializer, 'dgdx_dict': dgdx_dict_initializer, 'dgdx_Xindices_dict': dgdx_Xindices_dict_initializer, 'dgdx_Eindices_dict': dgdx_Eindices_dict_initializer, 'maskdict': maskdictInitializer, 'tensordict': tensordictInitializer, 'y_': y_Initializer, 'input_keep_prob_in': input_keep_prob_inInitializer, 'keep_prob_in': keep_prob_inInitializer, 'nAtoms_in': nAtoms_inInitializer, 'batchsizeInput': batchsizeInputInitializer, 'learningrate': learningrateInitializer, 'forces_in': forces_inInitializer, 'energycoefficient': energycoefficientInitializer, 'forcecoefficient': forcecoefficientInitializer, 'energyProdScale': energyProdScaleInitializer, 'totalNumAtoms': totalNumAtomsInitializer, 'nAtoms_forces': nAtoms_forces_Initializer} else: self.y_ = \ tf.placeholder(self.parameters['unit_type'], shape=[None, 1], name='y_') self.input_keep_prob_in = \ tf.placeholder(self.parameters['unit_type'], name='input_keep_prob_in') self.keep_prob_in = \ tf.placeholder(self.parameters['unit_type'], name='keep_prob_in') self.nAtoms_in = \ tf.placeholder(self.parameters['unit_type'], shape=[None, 1], name='nAtoms_in') self.batchsizeInput = \ tf.placeholder("int32", name='batchsizeInput') self.learningrate = \ tf.placeholder(self.parameters['unit_type'], name='learningrate') self.forces_in = \ tf.placeholder(self.parameters['unit_type'], shape=[None, None, 3], name='forces_in') self.energycoefficient = \ tf.placeholder(self.parameters['unit_type']) self.forcecoefficient = \ tf.placeholder(self.parameters['unit_type']) self.energyProdScale = \ tf.placeholder(self.parameters['unit_type'], name='energyProdScale') self.totalNumAtoms = \ tf.placeholder("int32", name='totalNumAtoms') self.nAtoms_forces = \ tf.placeholder(self.parameters['unit_type'], shape=[None, 1], name='totalNumAtoms') # Generate a multilayer neural network for each element type. outdict = {} forcedict = {} l2_regularization_dict = {} for element in self.elements: if isinstance(self.hiddenlayers, dict): networkListToUse = self.hiddenlayers[element] else: networkListToUse = self.hiddenlayers (outdict[element], forcedict[element], l2_regularization_dict[element]) = \ model(tensordict[element], indsdict[element], self.keep_prob_in, self.input_keep_prob_in, self.batchsizeInput, networkListToUse, self.activation, self.elementFingerprintLengths[ element], mask=maskdict[ element], name=self.saveVariableName, dgdx=self.dgdx_dict[ element], dgdx_Eindices=self.dgdx_Eindices_dict[ element], dgdx_Xindices=self.dgdx_Xindices_dict[ element], element=element, unit_type=self.parameters[ 'unit_type'], totalNumAtoms=self.totalNumAtoms) self.outdict = outdict # The total energy is the sum of the energies over each atom type. keylist = self.elements ytot = outdict[keylist[0]] for i in range(1, len(keylist)): ytot = ytot + outdict[keylist[i]] self.energy = ytot * self.energyProdScale # The total force is the sum of the forces over each atom type. Ftot = forcedict[keylist[0]] for i in range(1, len(keylist)): Ftot = Ftot + forcedict[keylist[i]] self.forcedict = forcedict self.forces = -Ftot * self.energyProdScale l2_regularization = l2_regularization_dict[keylist[0]] for i in range(1, len(keylist)): l2_regularization = l2_regularization + \ l2_regularization_dict[keylist[i]] # Define output nodes for the energy of a configuration, a loss # function, and the loss per atom (which is what we usually track) # self.loss = tf.sqrt(tf.reduce_sum( # tf.square(tf.sub(self.energy, self.y_)))) # self.lossPerAtom = tf.reduce_sum( # tf.square(tf.div(tf.sub(self.energy, self.y_), self.nAtoms_in))) # loss function, as included in model/__init__.py self.energy_loss = tf.reduce_sum( tf.square(tf.div(tf.sub(self.energy, self.y_), self.nAtoms_in))) # Define the training step for energy training. # self.loss_forces = self.forcecoefficient * \ # tf.sqrt(tf.reduce_mean(tf.square(tf.sub(self.forces_in, # self.forces)))) # force loss function, as included in model/__init__.py if self.parameters['relativeForceCutoff'] is None: self.force_loss = tf.reduce_sum( tf.div(tf.square(tf.sub(self.forces_in, self.forces)), self.nAtoms_forces)) / 3. # tf.reduce_sum(tf.div( # tf.reduce_mean(tf.square(tf.sub(self.forces_in, # self.forces)), 2), self.nAtoms_in)) else: relativeA = self.parameters['relativeForceCutoff'] self.force_loss = \ tf.reduce_sum(tf.div(tf.div( tf.square( tf.sub( self.forces_in, self.forces)), tf.square( self.forces_in) + relativeA**2.) * relativeA**2., self.nAtoms_forces)) / 3. # tf.reduce_sum(tf.div(tf.reduce_mean( # tf.div(tf.square(tf.sub(self.forces_in, self.forces)), # tf.square(self.forces_in)+relativeA**2.)*relativeA**2.,2), # self.nAtoms_in)) # Define max residuals self.energy_maxresid = tf.reduce_max( tf.abs(tf.div(tf.sub(self.energy, self.y_), self.nAtoms_in))) self.force_maxresid = tf.reduce_max( tf.abs(tf.sub(self.forces_in, self.forces))) # Define the training step for force training. if self.parameters['regularization_strength'] is not None: self.loss = self.forcecoefficient * self.force_loss + \ self.energycoefficient * self.energy_loss + \ self.parameters[ 'regularization_strength'] * l2_regularization self.energy_loss_regularized = self.energy_loss + \ self.parameters[ 'regularization_strength'] * l2_regularization else: self.loss = self.forcecoefficient * self.force_loss + \ self.energycoefficient * self.energy_loss self.energy_loss_regularized = self.energy_loss self.adam_optimizer_instance = \ tf.train.AdamOptimizer(self.learningrate, **self.parameters[ 'ADAM_optimizer_params']) self.train_step = \ self.adam_optimizer_instance.minimize( self.energy_loss_regularized) self.train_step_forces = \ self.adam_optimizer_instance.minimize(self.loss) # self.loss_forces_relative = \ # self.forcecoefficient * \ # tf.sqrt(tf.reduce_mean(tf.square(tf.div(tf.sub(self.forces_in, # self.forces),self.forces_in+0.0001)))) # self.force_loss_relative = \ # tf.reduce_sum(tf.div(tf.reduce_mean( # tf.div(tf.square(tf.sub(self.forces_in, # self.forces)),tf.square(self.forces_in)+0.005**2.),2), # self.nAtoms_in)) # self.loss_relative = \ # self.forcecoefficient*self.loss_forces_relative + \ # self.energycoefficient*self.energy_loss # self.train_step_forces = # tf.adam_optimizer_instance.minimize(self.loss_relative) def initializeVariables(self): """Resets all of the variables in the current tensorflow model.""" self.sess.run(tf.initialize_all_variables()) def generateFeedInput(self, curinds, energies, atomArraysAll, dgdx, dgdx_Eindices, dgdx_Xindices, nAtomsDict, atomsIndsReverse, batchsize, trainingrate, keepprob, inputkeepprob, natoms, forcesExp=0., forces=False, energycoefficient=1., forcecoefficient=None, training=True): """Generates the input dictionary that maps various inputs on the python side to placeholders for the tensorflow model.""" (atomArraysFinal, dgdx_batch, dgdx_Eindices_batch, dgdx_Xindices_batch, atomInds) = \ generateBatch(curinds, self.elements, atomArraysAll, nAtomsDict, atomsIndsReverse, dgdx, dgdx_Eindices, dgdx_Xindices) feedinput = {} for element in self.elements: if len(atomArraysFinal[element]) > 0: aAF = atomArraysFinal[element].copy() for i in range(len(aAF)): for j in range(len(aAF[i])): if (self.parameters['fprange'][element][1][j] - self.parameters['fprange'][element][0][j]) > 10.**-8: aAF[i][j] = -1. + \ 2. * (atomArraysFinal[element][i][j] - self.parameters['fprange'][element][0][j]) / ( self.parameters['fprange'][element][1][j] - self.parameters['fprange'][element][0][j]) feedinput[self.tensordict[element]] = aAF feedinput[self.indsdict[element]] = atomInds[element] feedinput[self.maskdict[element]] = np.ones((batchsize, 1)) if forcecoefficient > 1.e-5: dgdx_to_scale = dgdx_batch[element] for i in range(dgdx_to_scale.shape[0]): for l in range(dgdx_to_scale.shape[1]): if (self.parameters['fprange'][element][1][l] - self.parameters['fprange'][element][0][l]) > 10.**-8: dgdx_to_scale[i][l][:] = \ 2. * dgdx_to_scale[i][l][:] / \ (self.parameters['fprange'][element][1][l] - self.parameters['fprange'][element][0][l]) feedinput[self.dgdx_dict[element]] = dgdx_to_scale feedinput[self.dgdx_Eindices_dict[ element]] = dgdx_Eindices_batch[element] feedinput[self.dgdx_Xindices_dict[ element]] = dgdx_Xindices_batch[element] else: feedinput[self.dgdx_dict[element]] = \ np.zeros((len(dgdx_Eindices[element]), self.elementFingerprintLengths[element], 3)) feedinput[self.dgdx_Eindices_dict[element]] = [] feedinput[self.dgdx_Xindices_dict[element]] = [] else: feedinput[self.tensordict[element]] = np.zeros( (1, self.elementFingerprintLengths[element])) feedinput[self.indsdict[element]] = [0] feedinput[self.maskdict[element]] = np.zeros((batchsize, 1)) feedinput[self.dgdx_dict[element]] = \ np.zeros((len(dgdx_Eindices[element]), self.elementFingerprintLengths[element], 3)) feedinput[self.dgdx_Eindices_dict[element]] = [] feedinput[self.dgdx_Xindices_dict[element]] = [] feedinput[self.batchsizeInput] = batchsize feedinput[self.learningrate] = trainingrate feedinput[self.keep_prob_in] = keepprob feedinput[self.input_keep_prob_in] = inputkeepprob natoms_forces = [] for natom in natoms[curinds]: for i in range(natom): natoms_forces.append(natom) natoms_forces = np.array(natoms_forces) feedinput[self.nAtoms_forces] = natoms_forces feedinput[self.nAtoms_in] = natoms[curinds] feedinput[self.totalNumAtoms] = np.sum(natoms[curinds]) if training: feedinput[self.y_] = energies[curinds] if forcecoefficient > 1.e-5: feedinput[self.forces_in] = np.concatenate( forcesExp[curinds], axis=0) feedinput[self.forcecoefficient] = forcecoefficient feedinput[self.energycoefficient] = energycoefficient feedinput[self.energyProdScale] = self.parameters['energyProdScale'] return feedinput def fit(self, trainingimages, descriptor, parallel, log=None): """Fit takes a bunch of training images (which are assumed to have a working calculator attached), and fits the internal variables to the training images. """ # if self.graph is None, the module hasn't been initialized if self.graph is None: self.elementFingerprintLengths = {} for element in descriptor.parameters.Gs: self.elementFingerprintLengths[element] = len( descriptor.parameters.Gs[element]) self.elements = self.elementFingerprintLengths.keys() self.elements.sort() self.constructSessGraphModel(self.tfVars, self.sess) self.log = log params = self.parameters lf = LossFunction(convergence=params['convergence'], energy_coefficient=params['energy_coefficient'], force_coefficient=params['force_coefficient'], parallel={'cores': 1}) if params['force_coefficient'] is not None: lf.attach_model(self, images=trainingimages, fingerprints=descriptor.fingerprints, fingerprintprimes=descriptor.fingerprintprimes) else: lf.attach_model(self, images=trainingimages, fingerprints=descriptor.fingerprints) lf._initialize() # Inputs: # trainingimages: batchsize = self.batchsize if self.parameters['force_coefficient'] is None: fingerprintDerDB = None else: fingerprintDerDB = descriptor.fingerprintprimes images = trainingimages keylist = images.keys() fingerprintDB = descriptor.fingerprints self.parameters['numTrainingImages'] = len(keylist) (atomArraysAll, nAtomsDict, atomsIndsReverse, natoms, dgdx, dgdx_Eindices, dgdx_Xindices) = \ generateTensorFlowArrays(fingerprintDB, self.elements, keylist, fingerprintDerDB) energies = map( lambda x: [images[x].get_potential_energy(apply_constraint=False)], keylist) energies = np.array(energies) if self.parameters['preLoadTrainingData'] and not(self.miniBatch): numElements = {} for element in nAtomsDict: numElements[element] = sum(nAtomsDict[element]) self.saver.save(self.sess, 'tfAmpNN-checkpoint') with open('tfAmpNN-checkpoint') as fhandle: tfvars = fhandle.read() self.sess.close() numTrainingAtoms = np.sum(map(lambda x: len(images[x]), keylist)) num_dgdx_Eindices = {} num_dgdx_Xindices = {} for element in self.elements: num_dgdx_Eindices[element] = sum( map(len, dgdx_Eindices[element])) num_dgdx_Xindices[element] = sum( map(len, dgdx_Xindices[element])) self.constructSessGraphModel(tfvars, None, trainOnly=True, numElements=numElements, numTrainingImages=len(keylist), num_dgdx_Eindices=num_dgdx_Eindices, numTrainingAtoms=numTrainingAtoms) natomsArray = np.zeros((len(keylist), len(self.elements))) for i in range(len(images)): for j in range(len(self.elements)): natomsArray[i][j] = nAtomsDict[self.elements[j]][i] (atomArraysAll, nAtomsDict, atomsIndsReverse, natoms, dgdx, dgdx_Eindices, dgdx_Xindices) = generateTensorFlowArrays(fingerprintDB, self.elements, keylist, fingerprintDerDB) self.parameters['energyMeanScale'] = np.mean(energies) energies = energies - self.parameters['energyMeanScale'] self.parameters['energyProdScale'] = np.mean(np.abs(energies)) self.parameters['fprange'] = {} for element in self.elements: if len(atomArraysAll[element]) == 0: self.parameters['fprange'][element] = [] else: self.parameters['fprange'][element] = \ [np.min(atomArraysAll[element], axis=0), np.max(atomArraysAll[element], axis=0)] if self.parameters['force_coefficient'] is not None: # forces = map(lambda x: images[x].get_forces( # apply_constraint=False), keylist) # forces = np.zeros((len(keylist), self.maxAtomsForces, 3)) forces = [] for i in range(len(keylist)): atoms = images[keylist[i]] forces.append(atoms.get_forces(apply_constraint=False)) forces = np.array(forces) else: forces = 0. if not(self.miniBatch): batchsize = len(keylist) def trainmodel(trainingrate, keepprob, inputkeepprob, maxepochs): icount = 1 icount_global = 1 indlist = np.arange(len(keylist)) converge_save = [] # continue taking training steps as long as we haven't hit the RMSE # minimum of the max number of epochs while (icount < maxepochs): # if we're in minibatch mode, shuffle the index list if self.miniBatch: np.random.shuffle(indlist) for i in range(int(len(keylist) / batchsize)): # if we're doing minibatch, construct a new set of inputs if self.miniBatch or (not(self.miniBatch)and(icount == 1)): if self.miniBatch: curinds = indlist[ np.arange(batchsize) + i * batchsize] else: curinds = range(len(keylist)) feedinput = self.generateFeedInput( curinds, energies, atomArraysAll, dgdx, dgdx_Eindices, dgdx_Xindices, nAtomsDict, atomsIndsReverse, batchsize, trainingrate, keepprob, inputkeepprob, natoms, forcesExp=forces, energycoefficient=self.parameters[ 'energy_coefficient'], forcecoefficient=self.parameters[ 'force_coefficient']) if (self.parameters['preLoadTrainingData'] and not(self.miniBatch)): self.preLoadFeed(feedinput) # run a training step with the inputs. if self.parameters['force_coefficient'] is None: self.sess.run(self.train_step, feed_dict=feedinput) else: self.sess.run(self.train_step_forces, feed_dict=feedinput) # Print the loss function every 100 evals. # if (self.miniBatch)and(icount % 100 == 0): # feed_keepprob_save=feedinput[self.keep_prob_in] # feed_keepprob_save_input=\ # feedinput[self.input_keep_prob_in] # feedinput[self.keep_prob_in]=1. # feedinput[self.keep_prob_in]=feed_keepprob_save icount += 1 # Every 10 epochs, report the RMSE on the entire training set if icount_global % 10 == 0: if self.miniBatch: feedinput = self.generateFeedInput( range(len(keylist)), energies, atomArraysAll, dgdx, dgdx_Eindices, dgdx_Xindices, nAtomsDict, atomsIndsReverse, len(keylist), trainingrate, 1., 1., natoms, forcesExp=forces, energycoefficient=self.parameters[ 'energy_coefficient'], forcecoefficient=self.parameters[ 'force_coefficient'], ) feed_keepprob_save = feedinput[self.keep_prob_in] feed_keepprob_save_input = feedinput[ self.input_keep_prob_in] feedinput[self.keep_prob_in] = 1. feedinput[self.input_keep_prob_in] = 1. if self.parameters['force_coefficient'] is not None: converge_save.append( [self.sess.run(self.loss, feed_dict=feedinput), self.sess.run( self.energy_loss, feed_dict=feedinput), self.sess.run( self.force_loss, feed_dict=feedinput), self.sess.run( self.energy_maxresid, feed_dict=feedinput), self.sess.run(self.force_maxresid, feed_dict=feedinput)]) if len(converge_save) > 2: converge_save.pop(0) convergence_vals = np.mean(converge_save, 0) converged = lf.check_convergence(*convergence_vals) if converged: raise ConvergenceOccurred() else: converged = \ lf.check_convergence( self.sess.run(self.energy_loss, feed_dict=feedinput), self.sess.run(self.energy_loss, feed_dict=feedinput), 0., self.sess.run(self.energy_maxresid, feed_dict=feedinput), 0.) if converged: raise ConvergenceOccurred() feedinput[self.keep_prob_in] = keepprob feedinput[self.input_keep_prob_in] = inputkeepprob icount_global += 1 return def trainmodelBFGS(maxEpochs): curinds = range(len(keylist)) feedinput = self.generateFeedInput( curinds, energies, atomArraysAll, dgdx, dgdx_Eindices, dgdx_Xindices, nAtomsDict, atomsIndsReverse, batchsize, 1., 1., 1., natoms, forcesExp=forces, energycoefficient=self.parameters[ 'energy_coefficient'], forcecoefficient=self.parameters['force_coefficient']) def step_callbackfun_forces(x): evalvarlist = map(lambda y: float(np.array(y(x))), varlist) converged = lf.check_convergence(*evalvarlist) if converged: raise ConvergenceOccurred() def step_callbackfun_noforces(x): converged = \ lf.check_convergence(float(np.array(varlist[1](x))), float(np.array(varlist[1](x))), 0., float(np.array(varlist[3](x))), 0.) if converged: raise ConvergenceOccurred() if self.parameters['force_coefficient'] is None: step_callbackfun = step_callbackfun_noforces curloss = self.energy_loss else: step_callbackfun = step_callbackfun_forces curloss = self.loss if self.parameters['preLoadTrainingData'] and not(self.miniBatch): self.preLoadFeed(feedinput) extOpt = \ ScipyOptimizerInterface(curloss, method='l-BFGS-b', options={'maxiter': maxEpochs, 'ftol': 1.e-10, 'gtol': 1.e-10, 'factr': 1.e4}) varlist = [] for var in [self.loss, self.energy_loss, self.force_loss, self.energy_maxresid, self.force_maxresid]: if (self.parameters['preLoadTrainingData'] and (not self.miniBatch)): varlist.append( extOpt._make_eval_func(var, self.sess, {}, [])) else: varlist.append(extOpt._make_eval_func(var, self.sess, feedinput, [])) extOpt.minimize(self.sess, feed_dict=feedinput, step_callback=step_callbackfun) return try: if self.optimizationMethod == 'l-BFGS-b': with self.graph.as_default(): trainmodelBFGS(self.maxTrainingEpochs) elif self.optimizationMethod == 'ADAM': trainmodel(self.initialTrainingRate, self.keep_prob, self.input_keep_prob, self.maxTrainingEpochs) else: log('uknown optimizer!') except ConvergenceOccurred: if self.parameters['preLoadTrainingData'] and not(self.miniBatch): self.saver.save(self.sess, 'tfAmpNN-checkpoint') with open('tfAmpNN-checkpoint') as fhandle: tfvars = fhandle.read() self.constructSessGraphModel(tfvars, None, trainOnly=False) return True return False def preLoadFeed(self, feedinput): for element in self.dgdx_dict: if self.dgdx_dict[element] in feedinput: self.sess.run(self.dgdx_dict[element].initializer, feed_dict={ self.initializers['dgdx_dict'][element]: feedinput[self.dgdx_dict[element]]}) self.sess.run(self.dgdx_Eindices_dict[element].initializer, feed_dict={ self.initializers['dgdx_Eindices_dict'][element]: feedinput[self.dgdx_Eindices_dict[element]]}) self.sess.run(self.dgdx_Xindices_dict[element].initializer, feed_dict={ self.initializers['dgdx_Xindices_dict'][element]: feedinput[self.dgdx_Xindices_dict[element]]}) del feedinput[self.dgdx_dict[element]] del feedinput[self.dgdx_Eindices_dict[element]] del feedinput[self.dgdx_Xindices_dict[element]] self.sess.run(self.tensordict[element].initializer, feed_dict={ self.initializers['tensordict'][element]: feedinput[self.tensordict[element]]}) self.sess.run(self.indsdict[element].initializer, feed_dict={ self.initializers['indsdict'][element]: feedinput[self.indsdict[element]]}) self.sess.run(self.maskdict[element].initializer, feed_dict={ self.initializers['maskdict'][element]: feedinput[self.maskdict[element]]}) del feedinput[self.tensordict[element]] del feedinput[self.indsdict[element]] del feedinput[self.maskdict[element]] self.sess.run(self.y_.initializer, feed_dict={ self.initializers['y_']: feedinput[self.y_]}) self.sess.run(self.input_keep_prob_in.initializer, feed_dict={ self.initializers['input_keep_prob_in']: feedinput[self.input_keep_prob_in]}) self.sess.run(self.keep_prob_in.initializer, feed_dict={ self.initializers['keep_prob_in']: feedinput[self.keep_prob_in]}) self.sess.run(self.nAtoms_in.initializer, feed_dict={ self.initializers['nAtoms_in']: feedinput[self.nAtoms_in]}) self.sess.run(self.batchsizeInput.initializer, feed_dict={ self.initializers['batchsizeInput']: feedinput[self.batchsizeInput]}) self.sess.run(self.learningrate.initializer, feed_dict={ self.initializers['learningrate']: feedinput[self.learningrate]}) self.sess.run(self.totalNumAtoms.initializer, feed_dict={ self.initializers['totalNumAtoms']: feedinput[self.totalNumAtoms]}) self.sess.run(self.nAtoms_forces.initializer, feed_dict={ self.initializers['nAtoms_forces']: feedinput[self.nAtoms_forces]}) if self.forces_in in feedinput: self.sess.run(self.forces_in.initializer, feed_dict={ self.initializers['forces_in']: feedinput[self.forces_in]}) self.sess.run(self.energycoefficient.initializer, feed_dict={ self.initializers['energycoefficient']: feedinput[self.energycoefficient]}) self.sess.run(self.forcecoefficient.initializer, feed_dict={ self.initializers['forcecoefficient']: feedinput[self.forcecoefficient]}) self.sess.run(self.energyProdScale.initializer, feed_dict={ self.initializers['energyProdScale']: feedinput[self.energyProdScale]}) # feeedinput={} def get_energy_list(self, hashs, fingerprintDB, fingerprintDerDB=None, keep_prob=1., input_keep_prob=1., forces=False, nsamples=1): """Methods to get the energy and forces for a set of configurations.""" # Make images a list in case we've been passed a single hash to # calculate. if not(isinstance(hashs, list)): hashs = [hashs] # Reformat the image and fingerprint data into something we can pass # into tensorflow. (atomArraysAll, nAtomsDict, atomsIndsReverse, natoms, dgdx, dgdx_Eindices, dgdx_Xindices) = \ generateTensorFlowArrays(fingerprintDB, self.elements, hashs, fingerprintDerDB) energies = np.zeros(len(hashs)) forcelist = np.zeros(len(hashs)) curinds = range(len(hashs)) (atomArraysFinal, dgdx_batch, dgdx_Eindices_batch, dgdx_Xindices_batch, atomInds) = generateBatch(curinds, self.elements, atomArraysAll, nAtomsDict, atomsIndsReverse, dgdx, dgdx_Eindices, dgdx_Xindices) feedinput = self.generateFeedInput(curinds, energies, atomArraysAll, dgdx, dgdx_Eindices, dgdx_Xindices, nAtomsDict, atomsIndsReverse, len(hashs), 1., 1., 1., natoms, forcesExp=forcelist, energycoefficient=1., forcecoefficient=int(forces), training=False) if self.weights is not None: self.setWeightsScalings(feedinput, self.weights, self.scalings) if nsamples == 1: energies = \ np.array(self.sess.run(self.energy, feed_dict=feedinput)) + \ self.parameters['energyMeanScale'] # Add in the per-atom base energy. natomsArray = np.zeros((len(hashs), len(self.elements))) for i in range(len(hashs)): for j in range(len(self.elements)): natomsArray[i][j] = nAtomsDict[self.elements[j]][i] if forces: force = self.sess.run(self.forces, feed_dict=feedinput) force = reorganizeForces(force, natoms) else: force = [] else: energysave = [] forcesave = [] # Add in the per-atom base energy. natomsArray = np.zeros((len(hashs), len(self.elements))) for i in range(len(hashs)): for j in range(len(self.elements)): natomsArray[i][j] = nAtomsDict[self.elements[j]][i] for samplenum in range(nsamples): energies = \ np.array(self.sess.run(self.energy, feed_dict=feedinput)) + \ self.parameters['energyMeanScale'] energysave.append(map(lambda x: x[0], energies)) if forces: force = self.sess.run(self.forces, feed_dict=feedinput) forcesave.append(reorganizeForces(force, natoms)) energies = np.array(energysave) force = np.array(forcesave) return energies, force def calculate_energy(self, fingerprint): """Get the energy by feeding in a list to the get_list version (which is more efficient for anything greater than 1 image).""" key = '1' energies, forces = self.get_energy_list([key], {key: fingerprint}) return energies[0] def getVariance(self, fingerprint, nSamples=10, l=1.): key = '1' # energies=[] # for i in range(nSamples): # energies.append(self.get_energy_list([key], {key: # fingerprint},keep_prob=self.keep_prob)[0]) energies, force = \ self.get_energy_list([key], {key: fingerprint}, keep_prob=self.keep_prob, nsamples=nSamples) if (('regularization_strength' in self.parameters) and (self.parameters['regularization_strength'] is not None)): tau = l**2. * self.keep_prob / \ (2 * self.parameters['numTrainingImages'] * self.parameters['regularization_strength']) var = np.var(energies) + tau**-1. # forcevar=np.var(forces,) else: tau = 1 var = np.var(energies) return var def calculate_forces(self, fingerprint, derfingerprint): # calculate_forces function still needs to be implemented. Can't do # this without the fingerprint derivates working properly though key = '1' energies, forces = \ self.get_energy_list([key], {key: fingerprint}, fingerprintDerDB={key: derfingerprint}, forces=True) return forces[0][0:len(fingerprint)] def tostring(self): """Dummy tostring to make things work.""" params = {} params['hiddenlayers'] = self.hiddenlayers params['keep_prob'] = self.keep_prob params['input_keep_prob'] = self.input_keep_prob params['elementFingerprintLengths'] = self.elementFingerprintLengths params['batchsize'] = self.batchsize params['maxTrainingEpochs'] = self.maxTrainingEpochs params['importname'] = self.importname params['initialTrainingRate'] = self.initialTrainingRate params['activation'] = self.activationName params['saveVariableName'] = self.saveVariableName params['parameters'] = self.parameters params['miniBatch'] = self.miniBatch params['optimizationMethod'] = self.optimizationMethod # Create a string format of the tensorflow variables. self.saver.save(self.sess, 'tfAmpNN-checkpoint') with open('tfAmpNN-checkpoint') as fhandle: params['tfVars'] = fhandle.read() return str(params) def model(x, segmentinds, keep_prob, input_keep_prob, batchsize, neuronList, activationType, fplength, mask, name, dgdx, dgdx_Xindices, dgdx_Eindices, element, unit_type, totalNumAtoms): """Generates a multilayer neural network with variable number of neurons, so that we have a template for each atom's NN.""" namefun = lambda x: '%s_%s_' % (name, element) + x nNeurons = neuronList[0] # Pass the input tensors through the first soft-plus layer W_fc = weight_variable( [fplength, nNeurons], name=namefun('Wfc0'), unit_type=unit_type) b_fc = bias_variable([nNeurons], name=namefun('bfc0'), unit_type=unit_type) input_dropout = tf.nn.dropout(x, input_keep_prob) # h_fc = activationType(tf.matmul(x, W_fc) + b_fc) h_fc = tf.nn.dropout( activationType(tf.matmul(input_dropout, W_fc) + b_fc), keep_prob) # l2_regularization=\ # tf.reduce_sum(tf.square(W_fc))+tf.reduce_sum(tf.square(b_fc)) l2_regularization = tf.reduce_sum(tf.square(W_fc)) if len(neuronList) > 1: for i in range(1, len(neuronList)): nNeurons = neuronList[i] nNeuronsOld = neuronList[i - 1] W_fc = weight_variable([nNeuronsOld, nNeurons], name=namefun('Wfc%d' % i), unit_type=unit_type) b_fc = bias_variable([nNeurons], name=namefun('bfc%d' % i), unit_type=unit_type) h_fc = tf.nn.dropout(activationType( tf.matmul(h_fc, W_fc) + b_fc), keep_prob) l2_regularization += tf.reduce_sum( tf.square(W_fc)) + tf.reduce_sum(tf.square(b_fc)) W_fc_out = weight_variable( [neuronList[-1], 1], name=namefun('Wfcout'), unit_type=unit_type) b_fc_out = bias_variable([1], name=namefun('bfcout'), unit_type=unit_type) y_out = tf.matmul(h_fc, W_fc_out) + b_fc_out l2_regularization += tf.reduce_sum( tf.square(W_fc_out)) + tf.reduce_sum(tf.square(b_fc_out)) # l2_regularization+=tf.reduce_sum(tf.square(W_fc_out))) # Sum the predicted energy for each molecule reducedSum = tf.unsorted_segment_sum(y_out, segmentinds, batchsize) dEjdgj = tf.gradients(y_out, x)[0] # expand for 3 components (x,y,z) # dEjdgj1 = tf.expand_dims(dEjdgj, 2) # dEjdgjtile = tf.tile(dEjdgj1, [1,1,3]) # Gather rows necessary based on the given partial derivatives (dg/dx) dEdg_arranged = tf.gather(dEjdgj, dgdx_Eindices) dEdg_arranged_expand = tf.expand_dims(dEdg_arranged, 2) dEdg_arranged_tile = tf.tile(dEdg_arranged_expand, [1, 1, 3]) # multiply through with the dg/dx tensor, and sum along the components of g # to get a tensor of dE/dx (one row per atom considered, second dim =3) dEdx = tf.reduce_sum(tf.mul(dEdg_arranged_tile, dgdx), 1) # this should be a tensor of size (total atoms in training set)x3, # representing the contribution of each atom to the total energy via # interactions with elements of the current atom type dEdx_arranged = tf.unsorted_segment_sum(dEdx, dgdx_Xindices, totalNumAtoms) return tf.mul(reducedSum, mask), dEdx_arranged, l2_regularization # dEg # dEjdgj1 = tf.expand_dims(dEjdgj, 1) # dEjdgj2 = tf.expand_dims(dEjdgj1, 1) # dEjdgjtile = tf.tile(dEjdgj2, tilederiv) # dEdxik = tf.mul(dxdxik, dEjdgjtile) # dEdxikReduce = tf.reduce_sum(dEdxik, 3) # dEdxik_reduced = tf.unsorted_segment_sum( # dEdxikReduce, segmentinds, batchsize) # return tf.mul(reducedSum, mask), dEdxik_reduced,l2_regularization def weight_variable(shape, name, unit_type, stddev=0.1): """Helper functions taken from the MNIST tutorial to generate weight and bias variables with random initial weights.""" initial = tf.truncated_normal(shape, stddev=stddev, dtype=unit_type) return tf.Variable(initial, name=name) def bias_variable(shape, name, unit_type, a=0.1): """Helper functions taken from the MNIST tutorial to generate weight and bias variables with random initial weights.""" initial = tf.truncated_normal(stddev=a, shape=shape, dtype=unit_type) return tf.Variable(initial, name=name) def generateBatch(curinds, elements, atomArraysAll, nAtomsDict, atomsIndsReverse, dgdx, dgdx_Eindices, dgdx_Xindices,): """This method generates batches from a large dataset using a set of selected indices curinds.""" # inputs: atomArraysFinal = {} for element in elements: validKeys = np.in1d(atomsIndsReverse[element], curinds) if len(validKeys) > 0: atomArraysFinal[element] = atomArraysAll[element][validKeys] else: atomArraysFinal[element] = [] dgdx_out = {} dgdx_Eindices_out = {} dgdx_Xindices_out = {} for element in elements: if len(dgdx[element]) > 0: dgdx_out[element] = [] dgdx_Eindices_out[element] = [] dgdx_Xindices_out[element] = [] cursumE = 0 cursumX = 0 for curind in curinds: natomsElement = nAtomsDict[element][curind] natomsTotal = np.sum( map(lambda x: nAtomsDict[x][curind], elements)) if len(dgdx_Eindices[element][curind]) > 0: dgdx_out[element].append(dgdx[element][curind]) dgdx_Eindices_out[element].append( dgdx_Eindices[element][curind] + cursumE) dgdx_Xindices_out[element].append( dgdx_Xindices[element][curind] + cursumX) cursumE += natomsElement cursumX += natomsTotal if len(dgdx_out[element]) > 0: dgdx_out[element] = np.concatenate(dgdx_out[element], axis=0) dgdx_Eindices_out[element] = np.concatenate( dgdx_Eindices_out[element], axis=0) dgdx_Xindices_out[element] = np.concatenate( dgdx_Xindices_out[element], axis=0) else: dgdx_out[element] = np.array([[]]) dgdx_Eindices_out[element] = np.array([]) dgdx_Xindices_out[element] = np.array([]) else: dgdx_out[element] = np.array([[[]]]) dgdx_Eindices_out[element] = np.array([]) dgdx_Xindices_out[element] = np.array([]) atomInds = {} for element in elements: validKeys = np.in1d(atomsIndsReverse[element], curinds) if len(validKeys) > 0: atomIndsTemp = np.sum(atomsIndsReverse[element][validKeys], 1) atomInds[element] = atomIndsTemp * 0. for i in range(len(curinds)): atomInds[element][atomIndsTemp == curinds[i]] = i else: atomInds[element] = [] return (atomArraysFinal, dgdx_out, dgdx_Eindices_out, dgdx_Xindices_out, atomInds) def generateTensorFlowArrays(fingerprintDB, elements, keylist, fingerprintDerDB=None): """ This function generates the inputs to the tensorflow graph for the selected images. The essential problem is that each neural network is associated with a specific element type. Thus, atoms in each ASE image need to be sent to different networks. Inputs: fingerprintDB: a database of fingerprints, as taken from the descriptor elements: a list of element types (e.g. 'C','O', etc) keylist: a list of hashs into the fingerprintDB that we want to create inputs for fingerprintDerDB: a database of fingerprint derivatives, as taken from the descriptor maxAtomsForces: the maximum length of the atoms Outputs: atomArraysAll: a dictionary of fingerprint inputs to each element's neural network nAtomsDict: a dictionary for each element with lists of the number of atoms of each type in each image atomsIndsReverse: a dictionary that contains the index of each atom into the original keylist nAtoms: the number of atoms in each image atomArraysAllDerivs: dictionary of fingerprint derivates for each element's neural network """ nAtomsDict = {} for element in elements: nAtomsDict[element] = np.zeros(len(keylist)) for j in range(len(keylist)): fp = fingerprintDB[keylist[j]] atomSymbols, fpdata = zip(*fp) for i in range(len(fp)): nAtomsDict[atomSymbols[i]][j] += 1 atomsPositions = {} for element in elements: atomsPositions[element] = np.cumsum( nAtomsDict[element]) - nAtomsDict[element] atomsIndsReverse = {} for element in elements: atomsIndsReverse[element] = [] for i in range(len(keylist)): if nAtomsDict[element][i] > 0: atomsIndsReverse[element].append( np.ones((nAtomsDict[element][i].astype(np.int64), 1)) * i) if len(atomsIndsReverse[element]) > 0: atomsIndsReverse[element] = np.concatenate( atomsIndsReverse[element]) atomArraysAll = {} for element in elements: atomArraysAll[element] = [] natoms = np.zeros((len(keylist), 1)) for j in range(len(keylist)): fp = fingerprintDB[keylist[j]] atomSymbols, fpdata = zip(*fp) atomdata = zip(atomSymbols, range(len(atomSymbols))) for element in elements: atomArraysTemp = [] curatoms = [atom for atom in atomdata if atom[0] == element] for i in range(len(curatoms)): atomArraysTemp.append(fp[curatoms[i][1]][1]) if len(atomArraysTemp) > 0: atomArraysAll[element].append(atomArraysTemp) natoms[j] = len(atomSymbols) natomsposition = np.cumsum(natoms) - natoms[0] for element in elements: if len(atomArraysAll[element]) > 0: atomArraysAll[element] = np.concatenate(atomArraysAll[element]) else: atomArraysAll[element] = [] # Set up the array for atom-based fingerprint derivatives. dgdx = {} dgdx_Eindices = {} dgdx_Xindices = {} for element in elements: dgdx[element] = [] # Nxlen(fp)x3 array dgdx_Eindices[element] = [] # Nx1 array of which dE/dg to pull dgdx_Xindices[element] = [] # Nx1 array representing which atom this force will represent if fingerprintDerDB is not None: for j in range(len(keylist)): fp = fingerprintDB[keylist[j]] fpDer = fingerprintDerDB[keylist[j]] atomSymbols, fpdata = zip(*fp) atomdata = zip(atomSymbols, range(len(atomSymbols))) for element in elements: curatoms = [atom for atom in atomdata if atom[0] == element] dgdx_temp = [] dgdx_Eindices_temp = [] dgdx_Xindices_temp = [] if len(curatoms) > 0: for i in range(len(curatoms)): for k in range(len(atomdata)): # check if fp derivative is present dictkeys = [(k, atomdata[k][0], curatoms[ i][1], curatoms[i][0], 0), (k, atomdata[k][0], curatoms[ i][1], curatoms[i][0], 1), (k, atomdata[k][0], curatoms[ i][1], curatoms[i][0], 2)] if ((dictkeys[0] in fpDer) or (dictkeys[1] in fpDer) or (dictkeys[2] in fpDer)): fptemp = [] for ix in range(3): dictkey = (k, atomdata[k][0], curatoms[ i][1], curatoms[i][0], ix) fptemp.append(fpDer[dictkey]) dgdx_temp.append(np.array(fptemp).transpose()) dgdx_Eindices_temp.append(i) dgdx_Xindices_temp.append(k) if len(dgdx_Eindices_temp) > 0: dgdx[element].append(np.array(dgdx_temp)) dgdx_Eindices[element].append(np.array(dgdx_Eindices_temp)) dgdx_Xindices[element].append(np.array(dgdx_Xindices_temp)) else: dgdx[element].append([]) dgdx_Eindices[element].append([]) dgdx_Xindices[element].append([]) return (atomArraysAll, nAtomsDict, atomsIndsReverse, natoms, dgdx, dgdx_Eindices, dgdx_Xindices) def reorganizeForces(forces, natoms): curoffset = 0 forcelist = [] for N in natoms: forcelist.append(forces[curoffset:curoffset + N[0].astype(np.int64)]) curoffset += N[0] return forcelist amp-0.6/amp/model/Makefile0000644000175000017500000000011313137634440015331 0ustar muammarmuammarneuralnetwork.mod: gfortran -c neuralnetwork.f90 cp neuralnetwork.mod .. amp-0.6/amp/model/neuralnetwork.f900000644000175000017500000021762613137634440017134 0ustar muammarmuammar!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! module that utilizes the regression model to calculate energies ! and forces as well as their derivatives. Function names ending ! with an underscore correspond to image-centered mode. module neuralnetwork implicit none ! the data of neuralnetwork (should be fed in by python) double precision, allocatable::min_fingerprints(:, :) double precision, allocatable::max_fingerprints(:, :) integer, allocatable:: no_layers_of_elements(:) integer, allocatable:: no_nodes_of_elements(:) integer:: activation_signal type:: real_two_d_array sequence double precision, allocatable:: twodarray(:,:) end type real_two_d_array type:: element_parameters sequence double precision:: intercept double precision:: slope type(real_two_d_array), allocatable:: weights(:) end type element_parameters type:: real_one_d_array sequence double precision, allocatable:: onedarray(:) end type real_one_d_array contains !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Returns energy value in the image-centered mode. function calculate_image_energy(num_inputs, inputs, num_parameters, & parameters) implicit none integer:: num_inputs, num_parameters double precision:: inputs(num_inputs) double precision:: parameters(num_parameters) double precision:: calculate_image_energy integer:: p, m, n, layer integer:: l, j, num_rows, num_cols, q integer, allocatable:: hiddensizes(:) double precision, allocatable:: net(:) type(real_one_d_array), allocatable:: o(:), ohat(:) type(real_two_d_array), allocatable:: weights(:) double precision:: intercept double precision:: slope ! changing the form of parameters from vector into derived-types l = 0 allocate(weights(no_layers_of_elements(1)-1)) do j = 1, no_layers_of_elements(1) - 1 num_rows = no_nodes_of_elements(j) + 1 num_cols = no_nodes_of_elements(j + 1) allocate(weights(j)%twodarray(num_rows, num_cols)) do p = 1, num_rows do q = 1, num_cols weights(j)%twodarray(p, q) = & parameters(l + (p - 1) * num_cols + q) end do end do l = l + num_rows * num_cols end do intercept = parameters(l + 1) slope = parameters(l + 2) allocate(hiddensizes(no_layers_of_elements(1) - 2)) do m = 1, no_layers_of_elements(1) - 2 hiddensizes(m) = no_nodes_of_elements(m + 1) end do allocate(o(no_layers_of_elements(1))) allocate(ohat(no_layers_of_elements(1))) layer = 1 allocate(o(1)%onedarray(num_inputs)) allocate(ohat(1)%onedarray(num_inputs + 1)) do m = 1, num_inputs o(1)%onedarray(m) = inputs(m) end do do layer = 1, size(hiddensizes) + 1 do m = 1, size(weights(layer)%twodarray, dim=1) - 1 ohat(layer)%onedarray(m) = o(layer)%onedarray(m) end do ohat(layer)%onedarray(& size(weights(layer)%twodarray, dim=1)) = 1.0d0 allocate(net(size(weights(layer)%twodarray, dim=2))) allocate(o(layer + 1)%onedarray(& size(weights(layer)%twodarray, dim=2))) allocate(ohat(layer + 1)%onedarray(& size(weights(layer)%twodarray, dim=2) + 1)) do m = 1, size(weights(layer)%twodarray, dim=2) net(m) = 0.0d0 do n = 1, size(weights(layer)%twodarray, dim=1) net(m) = net(m) + & ohat(layer)%onedarray(n) & * weights(layer)%twodarray(n, m) end do if (activation_signal == 1) then o(layer + 1)%onedarray(m) = & tanh(net(m)) else if (activation_signal == 2) then o(layer + 1)%onedarray(m) = & 1.0d0 / (1.0d0 + exp(- net(m))) else if (activation_signal == 3) then o(layer + 1)%onedarray(m) = net(m) end if ohat(layer + 1)%onedarray(m) = o(layer + 1)%onedarray(m) end do ohat(layer + 1)%onedarray(& size(weights(layer)%twodarray, dim=2) + 1) = 1.0d0 deallocate(net) end do calculate_image_energy = slope * o(layer)%onedarray(1) + intercept ! deallocating neural network deallocate(hiddensizes) do p = 1, size(o) deallocate(o(p)%onedarray) end do deallocate(o) do p = 1, size(ohat) deallocate(ohat(p)%onedarray) end do deallocate(ohat) ! deallocating derived type parameters do p = 1, size(weights) deallocate(weights(p)%twodarray) end do deallocate(weights) end function calculate_image_energy !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Returns energy value in the atom-centered mode. function calculate_atomic_energy(symbol, & len_of_fingerprint, fingerprint, & num_elements, elements_numbers, & num_parameters, parameters) implicit none integer:: symbol, num_parameters, & len_of_fingerprint, num_elements double precision:: fingerprint(len_of_fingerprint) integer:: elements_numbers(num_elements) double precision:: parameters(num_parameters) double precision:: calculate_atomic_energy integer:: p, element, m, n, layer integer:: k, l, j, num_rows, num_cols, q integer, allocatable:: hiddensizes(:) double precision, allocatable:: net(:) type(real_one_d_array), allocatable:: o(:), ohat(:) type(element_parameters):: unraveled_parameters(num_elements) double precision:: fingerprint_(len_of_fingerprint) ! scaling fingerprints do element = 1, num_elements if (symbol == & elements_numbers(element)) then exit end if end do do l = 1, len_of_fingerprint if ((max_fingerprints(element, l) - & min_fingerprints(element, l)) .GT. & (10.0d0 ** (-8.0d0))) then fingerprint_(l) = -1.0d0 + 2.0d0 * & (fingerprint(l) - min_fingerprints(element, l)) / & (max_fingerprints(element, l) - & min_fingerprints(element, l)) else fingerprint_(l) = fingerprint(l) endif end do ! changing the form of parameters from vector into derived-types k = 0 l = 0 do element = 1, num_elements allocate(unraveled_parameters(element)%weights(& no_layers_of_elements(element)-1)) if (element .GT. 1) then k = k + no_layers_of_elements(element - 1) end if do j = 1, no_layers_of_elements(element) - 1 num_rows = no_nodes_of_elements(k + j) + 1 num_cols = no_nodes_of_elements(k + j + 1) allocate(unraveled_parameters(& element)%weights(j)%twodarray(num_rows, num_cols)) do p = 1, num_rows do q = 1, num_cols unraveled_parameters(element)%weights(j)%twodarray(& p, q) = parameters(l + (p - 1) * num_cols + q) end do end do l = l + num_rows * num_cols end do end do do element = 1, num_elements unraveled_parameters(element)%intercept = & parameters(l + 2 * element - 1) unraveled_parameters(element)%slope = & parameters(l + 2 * element) end do p = 0 do element = 1, num_elements if (symbol == elements_numbers(element)) then exit else p = p + no_layers_of_elements(element) end if end do allocate(hiddensizes(no_layers_of_elements(element) - 2)) do m = 1, no_layers_of_elements(element) - 2 hiddensizes(m) = no_nodes_of_elements(p + m + 1) end do allocate(o(no_layers_of_elements(element))) allocate(ohat(no_layers_of_elements(element))) layer = 1 allocate(o(1)%onedarray(len_of_fingerprint)) allocate(ohat(1)%onedarray(len_of_fingerprint + 1)) do m = 1, len_of_fingerprint o(1)%onedarray(m) = fingerprint_(m) end do do layer = 1, size(hiddensizes) + 1 do m = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim=1) - 1 ohat(layer)%onedarray(m) = o(layer)%onedarray(m) end do ohat(layer)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=1)) = 1.0d0 allocate(net(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2))) allocate(o(layer + 1)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2))) allocate(ohat(layer + 1)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2) + 1)) do m = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim=2) net(m) = 0.0d0 do n = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim=1) net(m) = net(m) + & ohat(layer)%onedarray(n) * unraveled_parameters(& element)%weights(layer)%twodarray(n, m) end do if (activation_signal == 1) then o(layer + 1)%onedarray(m) = tanh(net(m)) else if (activation_signal == 2) then o(layer + 1)%onedarray(m) = & 1.0d0 / (1.0d0 + exp(- net(m))) else if (activation_signal == 3) then o(layer + 1)%onedarray(m) = net(m) end if ohat(layer + 1)%onedarray(m) = o(layer + 1)%onedarray(m) end do ohat(layer + 1)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2) + 1) = 1.0d0 deallocate(net) end do calculate_atomic_energy = unraveled_parameters(element)%slope * & o(layer)%onedarray(1) + unraveled_parameters(element)%intercept ! deallocating neural network deallocate(hiddensizes) do p = 1, size(o) deallocate(o(p)%onedarray) end do deallocate(o) do p = 1, size(ohat) deallocate(ohat(p)%onedarray) end do deallocate(ohat) ! deallocating derived type parameters do element = 1, num_elements deallocate(unraveled_parameters(element)%weights) end do end function calculate_atomic_energy !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Returns force value in the image-centered mode. function calculate_force_(num_inputs, inputs, inputs_, & num_parameters, parameters) implicit none integer:: num_inputs, num_parameters double precision:: inputs(num_inputs) double precision:: inputs_(num_inputs) double precision:: parameters(num_parameters) double precision:: calculate_force_ double precision, allocatable:: temp(:) integer:: p, q, m, n, nn, layer integer:: l, j, num_rows, num_cols integer, allocatable:: hiddensizes(:) double precision, allocatable:: net(:) type(real_one_d_array), allocatable:: o(:), ohat(:) type(real_one_d_array), allocatable:: doutputs_dinputs(:) type(real_two_d_array), allocatable:: weights(:) double precision:: intercept double precision:: slope ! changing the form of parameters to derived-types l = 0 allocate(weights(no_layers_of_elements(1)-1)) do j = 1, no_layers_of_elements(1) - 1 num_rows = no_nodes_of_elements(j) + 1 num_cols = no_nodes_of_elements(j + 1) allocate(weights(j)%twodarray(num_rows, num_cols)) do p = 1, num_rows do q = 1, num_cols weights(j)%twodarray(p, q) = & parameters(l + (p - 1) * num_cols + q) end do end do l = l + num_rows * num_cols end do intercept = parameters(l + 1) slope = parameters(l + 2) allocate(hiddensizes(no_layers_of_elements(1) - 2)) do m = 1, no_layers_of_elements(1) - 2 hiddensizes(m) = no_nodes_of_elements(m + 1) end do allocate(o(no_layers_of_elements(1))) allocate(ohat(no_layers_of_elements(1))) layer = 1 allocate(o(1)%onedarray(num_inputs)) allocate(ohat(1)%onedarray(num_inputs + 1)) do m = 1, num_inputs o(1)%onedarray(m) = inputs(m) end do do layer = 1, size(hiddensizes) + 1 do m = 1, size(weights(layer)%twodarray, dim=1) - 1 ohat(layer)%onedarray(m) = o(layer)%onedarray(m) end do ohat(layer)%onedarray(& size(weights(layer)%twodarray, dim=1)) = 1.0d0 allocate(net(size(weights(layer)%twodarray, dim=2))) allocate(o(layer + 1)%onedarray(& size(weights(layer)%twodarray, dim=2))) allocate(ohat(layer + 1)%onedarray(& size(weights(layer)%twodarray, dim=2) + 1)) do m = 1, size(weights(layer)%twodarray, dim=2) net(m) = 0.0d0 do n = 1, size(weights(layer)%twodarray, dim=1) net(m) = net(m) + & ohat(layer)%onedarray(n) * & weights(layer)%twodarray(n, m) end do if (activation_signal == 1) then o(layer + 1)%onedarray(m) = tanh(net(m)) else if (activation_signal == 2) then o(layer + 1)%onedarray(m) = & 1.0d0 / (1.0d0 + exp(- net(m))) else if (activation_signal == 3) then o(layer + 1)%onedarray(m) = net(m) end if ohat(layer + 1)%onedarray(m) = o(layer + 1)%onedarray(m) end do deallocate(net) end do nn = size(o) - 2 allocate(doutputs_dinputs(nn + 2)) allocate(doutputs_dinputs(1)%onedarray(num_inputs)) do m = 1, num_inputs doutputs_dinputs(1)%onedarray(m) = inputs_(m) end do do layer = 1, nn + 1 allocate(temp(size(weights(layer)%twodarray, dim = 2))) do p = 1, size(weights(layer)%twodarray, dim = 2) temp(p) = 0.0d0 do q = 1, size(weights(layer)%twodarray, dim = 1) - 1 temp(p) = temp(p) + doutputs_dinputs(& layer)%onedarray(q) * weights(layer)%twodarray(q, p) end do end do q = size(o(layer + 1)%onedarray) allocate(doutputs_dinputs(layer + 1)%onedarray(q)) do p = 1, size(o(layer + 1)%onedarray) if (activation_signal == 1) then doutputs_dinputs(layer + 1)%onedarray(p) = & temp(p) * (1.0d0 - o(layer + 1)%onedarray(p) * & o(layer + 1)%onedarray(p)) else if (activation_signal == 2) then doutputs_dinputs(layer + 1)%onedarray(p) = & temp(p) * (1.0d0 - o(layer + 1)%onedarray(p)) * & o(layer + 1)%onedarray(p) else if (activation_signal == 3) then doutputs_dinputs(layer+ 1)%onedarray(p) = temp(p) end if end do deallocate(temp) end do calculate_force_ = slope * doutputs_dinputs(nn + 2)%onedarray(1) ! force is multiplied by -1, because it is -dE/dx and not dE/dx. calculate_force_ = -1.0d0 * calculate_force_ ! deallocating neural network deallocate(hiddensizes) do p = 1, size(o) deallocate(o(p)%onedarray) end do deallocate(o) do p = 1, size(ohat) deallocate(ohat(p)%onedarray) end do deallocate(ohat) do p = 1, size(doutputs_dinputs) deallocate(doutputs_dinputs(p)%onedarray) end do deallocate(doutputs_dinputs) ! deallocating derived type parameters do p = 1, size(weights) deallocate(weights(p)%twodarray) end do deallocate(weights) end function calculate_force_ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Returns force value in the atom-centered mode. function calculate_force(symbol, len_of_fingerprint, fingerprint, & fingerprintprime, num_elements, elements_numbers, & num_parameters, parameters) implicit none integer:: symbol, len_of_fingerprint, num_parameters integer:: num_elements double precision:: fingerprint(len_of_fingerprint) double precision:: fingerprintprime(len_of_fingerprint) integer:: elements_numbers(num_elements) double precision:: parameters(num_parameters) double precision:: calculate_force double precision, allocatable:: temp(:) integer:: p, q, element, m, n, nn, layer integer:: k, l, j, num_rows, num_cols integer, allocatable:: hiddensizes(:) double precision, allocatable:: net(:) type(real_one_d_array), allocatable:: o(:), ohat(:) type(real_one_d_array), allocatable:: doutputs_dinputs(:) type(element_parameters):: unraveled_parameters(num_elements) double precision:: fingerprint_(len_of_fingerprint) double precision:: fingerprintprime_(len_of_fingerprint) ! scaling fingerprints do element = 1, num_elements if (symbol == & elements_numbers(element)) then exit end if end do do l = 1, len_of_fingerprint if ((max_fingerprints(element, l) - & min_fingerprints(element, l)) .GT. & (10.0d0 ** (-8.0d0))) then fingerprint_(l) = -1.0d0 + 2.0d0 * & (fingerprint(l) - min_fingerprints(element, l)) / & (max_fingerprints(element, l) - & min_fingerprints(element, l)) else fingerprint_(l) = fingerprint(l) endif end do ! scaling fingerprintprimes do l = 1, len_of_fingerprint if ((max_fingerprints(element, l) - & min_fingerprints(element, l)) .GT. & (10.0d0 ** (-8.0d0))) then fingerprintprime_(l) = & 2.0d0 * fingerprintprime(l) / & (max_fingerprints(element, l) - & min_fingerprints(element, l)) else fingerprintprime_(l) = fingerprintprime(l) endif end do ! changing the form of parameters to derived-types k = 0 l = 0 do element = 1, num_elements allocate(unraveled_parameters(element)%weights(& no_layers_of_elements(element)-1)) if (element .GT. 1) then k = k + no_layers_of_elements(element - 1) end if do j = 1, no_layers_of_elements(element) - 1 num_rows = no_nodes_of_elements(k + j) + 1 num_cols = no_nodes_of_elements(k + j + 1) allocate(unraveled_parameters(& element)%weights(j)%twodarray(num_rows, num_cols)) do p = 1, num_rows do q = 1, num_cols unraveled_parameters(element)%weights(j)%twodarray(& p, q) = parameters(l + (p - 1) * num_cols + q) end do end do l = l + num_rows * num_cols end do end do do element = 1, num_elements unraveled_parameters(element)%intercept = & parameters(l + 2 * element - 1) unraveled_parameters(element)%slope = & parameters(l + 2 * element) end do p = 0 do element = 1, num_elements if (symbol == elements_numbers(element)) then exit else p = p + no_layers_of_elements(element) end if end do allocate(hiddensizes(no_layers_of_elements(element) - 2)) do m = 1, no_layers_of_elements(element) - 2 hiddensizes(m) = no_nodes_of_elements(p + m + 1) end do allocate(o(no_layers_of_elements(element))) allocate(ohat(no_layers_of_elements(element))) layer = 1 allocate(o(1)%onedarray(len_of_fingerprint)) allocate(ohat(1)%onedarray(len_of_fingerprint + 1)) do m = 1, len_of_fingerprint o(1)%onedarray(m) = fingerprint_(m) end do do layer = 1, size(hiddensizes) + 1 do m = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim=1) - 1 ohat(layer)%onedarray(m) = o(layer)%onedarray(m) end do ohat(layer)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=1)) = 1.0d0 allocate(net(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2))) allocate(o(layer + 1)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2))) allocate(ohat(layer + 1)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2) + 1)) do m = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim=2) net(m) = 0.0d0 do n = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim=1) net(m) = net(m) + & ohat(layer)%onedarray(n) * unraveled_parameters(& element)%weights(layer)%twodarray(n, m) end do if (activation_signal == 1) then o(layer + 1)%onedarray(m) = tanh(net(m)) else if (activation_signal == 2) then o(layer + 1)%onedarray(m) = & 1.0d0 / (1.0d0 + exp(- net(m))) else if (activation_signal == 3) then o(layer + 1)%onedarray(m) = net(m) end if ohat(layer + 1)%onedarray(m) = o(layer + 1)%onedarray(m) end do deallocate(net) end do nn = size(o) - 2 allocate(doutputs_dinputs(nn + 2)) allocate(doutputs_dinputs(1)%onedarray(& len_of_fingerprint)) do m = 1, len_of_fingerprint doutputs_dinputs(1)%onedarray(m) = fingerprintprime_(m) end do do layer = 1, nn + 1 allocate(temp(size(unraveled_parameters(element)%weights(& layer)%twodarray, dim = 2))) do p = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim = 2) temp(p) = 0.0d0 do q = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim = 1) - 1 temp(p) = temp(p) + doutputs_dinputs(& layer)%onedarray(q) * unraveled_parameters(& element)%weights(layer)%twodarray(q, p) end do end do q = size(o(layer + 1)%onedarray) allocate(doutputs_dinputs(layer + 1)%onedarray(q)) do p = 1, size(o(layer + 1)%onedarray) if (activation_signal == 1) then doutputs_dinputs(layer + 1)%onedarray(p) = temp(p) * & (1.0d0 - o(layer + 1)%onedarray(p) * & o(layer + 1)%onedarray(p)) else if (activation_signal == 2) then doutputs_dinputs(layer + 1)%onedarray(p) = & temp(p) * (1.0d0 - o(layer + 1)%onedarray(p)) * & o(layer + 1)%onedarray(p) else if (activation_signal == 3) then doutputs_dinputs(layer+ 1)%onedarray(p) = temp(p) end if end do deallocate(temp) end do calculate_force = unraveled_parameters(element)%slope * & doutputs_dinputs(nn + 2)%onedarray(1) ! force is multiplied by -1, because it is -dE/dx and not dE/dx. calculate_force = -1.0d0 * calculate_force ! deallocating neural network deallocate(hiddensizes) do p = 1, size(o) deallocate(o(p)%onedarray) end do deallocate(o) do p = 1, size(ohat) deallocate(ohat(p)%onedarray) end do deallocate(ohat) do p = 1, size(doutputs_dinputs) deallocate(doutputs_dinputs(p)%onedarray) end do deallocate(doutputs_dinputs) ! deallocating derived type parameters do element = 1, num_elements deallocate(unraveled_parameters(element)%weights) end do end function calculate_force !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Returns derivative of energy w.r.t. parameters in the ! image-centered mode. function calculate_denergy_dparameters_(num_inputs, inputs, & num_parameters, parameters) implicit none integer:: num_inputs, num_parameters double precision:: calculate_denergy_dparameters_(num_parameters) double precision:: parameters(num_parameters) double precision:: inputs(num_inputs) integer:: m, n, j, l, layer, p, q, nn, num_cols, num_rows double precision:: temp1, temp2 integer, allocatable:: hiddensizes(:) double precision, allocatable:: net(:) type(real_one_d_array), allocatable:: o(:), ohat(:) type(real_one_d_array), allocatable:: delta(:), D(:) type(real_two_d_array), allocatable:: weights(:) double precision:: intercept double precision:: slope type(real_two_d_array), allocatable:: & unraveled_denergy_dweights(:) double precision:: denergy_dintercept double precision:: denergy_dslope ! changing the form of parameters from vector into derived-types l = 0 allocate(weights(no_layers_of_elements(1)-1)) do j = 1, no_layers_of_elements(1) - 1 num_rows = no_nodes_of_elements(j) + 1 num_cols = no_nodes_of_elements(j + 1) allocate(weights(j)%twodarray(num_rows, num_cols)) do p = 1, num_rows do q = 1, num_cols weights(j)%twodarray(p, q) = & parameters(l + (p - 1) * num_cols + q) end do end do l = l + num_rows * num_cols end do intercept = parameters(l + 1) slope = parameters(l + 2) denergy_dintercept = 0.d0 denergy_dslope = 0.d0 l = 0 allocate(unraveled_denergy_dweights(no_layers_of_elements(1)-1)) do j = 1, no_layers_of_elements(1) - 1 num_rows = no_nodes_of_elements(j) + 1 num_cols = no_nodes_of_elements(j + 1) allocate(unraveled_denergy_dweights(j)%twodarray(num_rows, & num_cols)) do p = 1, num_rows do q = 1, num_cols unraveled_denergy_dweights(j)%twodarray(p, q) = 0.0d0 end do end do l = l + num_rows * num_cols end do allocate(hiddensizes(no_layers_of_elements(1) - 2)) do m = 1, no_layers_of_elements(1) - 2 hiddensizes(m) = no_nodes_of_elements(m + 1) end do allocate(o(no_layers_of_elements(1))) allocate(ohat(no_layers_of_elements(1))) layer = 1 allocate(o(1)%onedarray(num_inputs)) allocate(ohat(1)%onedarray(num_inputs + 1)) do m = 1, num_inputs o(1)%onedarray(m) = inputs(m) end do do layer = 1, size(hiddensizes) + 1 do m = 1, size(weights(layer)%twodarray, dim=1) - 1 ohat(layer)%onedarray(m) = o(layer)%onedarray(m) end do ohat(layer)%onedarray(& size(weights(layer)%twodarray, dim=1)) = 1.0d0 allocate(net(size(weights(layer)%twodarray, dim=2))) allocate(o(layer + 1)%onedarray(& size(weights(layer)%twodarray, dim=2))) allocate(ohat(layer + 1)%onedarray(& size(weights(layer)%twodarray, dim=2) + 1)) do m = 1, size(weights(layer)%twodarray, dim=2) net(m) = 0.0d0 do n = 1, size(weights(layer)%twodarray, dim=1) net(m) = net(m) + & ohat(layer)%onedarray(n) * weights(& layer)%twodarray(n, m) end do if (activation_signal == 1) then o(layer + 1)%onedarray(m) = tanh(net(m)) else if (activation_signal == 2) then o(layer + 1)%onedarray(m) = & 1.0d0 / (1.0d0 + exp(- net(m))) else if (activation_signal == 3) then o(layer + 1)%onedarray(m) = net(m) end if ohat(layer + 1)%onedarray(m) = o(layer + 1)%onedarray(m) end do ohat(layer + 1)%onedarray(& size(weights(layer)%twodarray, dim=2) + 1) = 1.0d0 deallocate(net) end do nn = size(o) - 2 allocate(D(nn + 1)) do layer = 1, nn + 1 allocate(D(layer)%onedarray(size(o(layer + 1)%onedarray))) do j = 1, size(o(layer + 1)%onedarray) if (activation_signal == 1) then D(layer)%onedarray(j) = & (1.0d0 - o(layer + 1)%onedarray(j)* & o(layer + 1)%onedarray(j)) elseif (activation_signal == 2) then D(layer)%onedarray(j) = o(layer + 1)%onedarray(j) * & (1.0d0 - o(layer + 1)%onedarray(j)) elseif (activation_signal == 3) then D(layer)%onedarray(j) = 1.0d0 end if end do end do allocate(delta(nn + 1)) allocate(delta(nn + 1)%onedarray(1)) delta(nn + 1)%onedarray(1) = D(nn + 1)%onedarray(1) do layer = nn, 1, -1 allocate(delta(layer)%onedarray(size(D(layer)%onedarray))) do p = 1, size(D(layer)%onedarray) delta(layer)%onedarray(p) = 0.0d0 do q = 1, size(delta(layer + 1)%onedarray) temp1 = D(layer)%onedarray(p) * & weights(layer + 1)%twodarray(p, q) temp2 = temp1 * delta(layer + 1)%onedarray(q) delta(layer)%onedarray(p) = & delta(layer)%onedarray(p) + temp2 end do end do end do denergy_dintercept = 1.0d0 denergy_dslope = o(nn + 2)%onedarray(1) do layer = 1, nn + 1 do p = 1, size(ohat(layer)%onedarray) do q = 1, size(delta(layer)%onedarray) unraveled_denergy_dweights(layer)%twodarray(p, q) = & slope * & ohat(layer)%onedarray(p) * delta(layer)%onedarray(q) end do end do end do deallocate(hiddensizes) do p = 1, size(o) deallocate(o(p)%onedarray) end do deallocate(o) do p = 1, size(ohat) deallocate(ohat(p)%onedarray) end do deallocate(ohat) do p = 1, size(delta) deallocate(delta(p)%onedarray) end do deallocate(delta) do p = 1, size(D) deallocate(D(p)%onedarray) end do deallocate(D) ! changing the derivatives of the energy from derived-type ! form into vector l = 0 do j = 1, no_layers_of_elements(1) - 1 num_rows = no_nodes_of_elements(j) + 1 num_cols = no_nodes_of_elements(j + 1) do p = 1, num_rows do q = 1, num_cols calculate_denergy_dparameters_(& l + (p - 1) * num_cols + q) = & unraveled_denergy_dweights(j)%twodarray(p, q) end do end do l = l + num_rows * num_cols end do calculate_denergy_dparameters_(l + 1) = denergy_dintercept calculate_denergy_dparameters_(l + 2) = denergy_dslope ! deallocating derived-type parameters do p = 1, size(weights) deallocate(weights(p)%twodarray) end do deallocate(weights) do p = 1, size(unraveled_denergy_dweights) deallocate(unraveled_denergy_dweights(p)%twodarray) end do deallocate(unraveled_denergy_dweights) end function calculate_denergy_dparameters_ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Returns derivative of energy w.r.t. parameters in the ! atom-centered mode. function calculate_datomicenergy_dparameters(symbol, & len_of_fingerprint, fingerprint, num_elements, & elements_numbers, num_parameters, parameters) implicit none integer:: num_parameters, num_elements integer:: symbol, len_of_fingerprint double precision:: calculate_datomicenergy_dparameters(num_parameters) double precision:: parameters(num_parameters) double precision:: fingerprint(len_of_fingerprint) integer:: elements_numbers(num_elements) integer:: element, m, n, j, k, l, layer, p, q, nn, num_cols integer:: num_rows double precision:: temp1, temp2 integer, allocatable:: hiddensizes(:) double precision, allocatable:: net(:) type(real_one_d_array), allocatable:: o(:), ohat(:) type(real_one_d_array), allocatable:: delta(:), D(:) type(element_parameters):: unraveled_parameters(num_elements) type(element_parameters):: & unraveled_daenergy_dparameters(num_elements) double precision:: fingerprint_(len_of_fingerprint) ! scaling fingerprints do element = 1, num_elements if (symbol == & elements_numbers(element)) then exit end if end do do l = 1, len_of_fingerprint if ((max_fingerprints(element, l) - & min_fingerprints(element, l)) .GT. & (10.0d0 ** (-8.0d0))) then fingerprint_(l) = -1.0d0 + 2.0d0 * & (fingerprint(l) - min_fingerprints(element, l)) / & (max_fingerprints(element, l) - & min_fingerprints(element, l)) else fingerprint_(l) = fingerprint(l) endif end do ! changing the form of parameters to derived types k = 0 l = 0 do element = 1, num_elements allocate(unraveled_parameters(element)%weights(& no_layers_of_elements(element)-1)) if (element .GT. 1) then k = k + no_layers_of_elements(element - 1) end if do j = 1, no_layers_of_elements(element) - 1 num_rows = no_nodes_of_elements(k + j) + 1 num_cols = no_nodes_of_elements(k + j + 1) allocate(unraveled_parameters(element)%weights(& j)%twodarray(num_rows, num_cols)) do p = 1, num_rows do q = 1, num_cols unraveled_parameters(element)%weights(j)%twodarray(& p, q) = parameters(l + (p - 1) * num_cols + q) end do end do l = l + num_rows * num_cols end do end do do element = 1, num_elements unraveled_parameters(element)%intercept = & parameters(l + 2 * element - 1) unraveled_parameters(element)%slope = & parameters(l + 2 * element) end do do element = 1, num_elements unraveled_daenergy_dparameters(element)%intercept = 0.d0 unraveled_daenergy_dparameters(element)%slope = 0.d0 end do k = 0 l = 0 do element = 1, num_elements allocate(unraveled_daenergy_dparameters(element)%weights(& no_layers_of_elements(element)-1)) if (element > 1) then k = k + no_layers_of_elements(element - 1) end if do j = 1, no_layers_of_elements(element) - 1 num_rows = no_nodes_of_elements(k + j) + 1 num_cols = no_nodes_of_elements(k + j + 1) allocate(unraveled_daenergy_dparameters(& element)%weights(j)%twodarray(num_rows, num_cols)) do p = 1, num_rows do q = 1, num_cols unraveled_daenergy_dparameters(& element)%weights(j)%twodarray(p, q) = 0.0d0 end do end do l = l + num_rows * num_cols end do end do p = 0 do element = 1, num_elements if (symbol == elements_numbers(element)) then exit else p = p + no_layers_of_elements(element) end if end do allocate(hiddensizes(no_layers_of_elements(element) - 2)) do m = 1, no_layers_of_elements(element) - 2 hiddensizes(m) = no_nodes_of_elements(p + m + 1) end do allocate(o(no_layers_of_elements(element))) allocate(ohat(no_layers_of_elements(element))) layer = 1 allocate(o(1)%onedarray(len_of_fingerprint)) allocate(ohat(1)%onedarray(len_of_fingerprint + 1)) do m = 1, len_of_fingerprint o(1)%onedarray(m) = fingerprint_(m) end do do layer = 1, size(hiddensizes) + 1 do m = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim=1) - 1 ohat(layer)%onedarray(m) = o(layer)%onedarray(m) end do ohat(layer)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=1)) = 1.0d0 allocate(net(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2))) allocate(o(layer + 1)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2))) allocate(ohat(layer + 1)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2) + 1)) do m = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim=2) net(m) = 0.0d0 do n = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim=1) net(m) = net(m) + & ohat(layer)%onedarray(n) * unraveled_parameters(& element)%weights(layer)%twodarray(n, m) end do if (activation_signal == 1) then o(layer + 1)%onedarray(m) = tanh(net(m)) else if (activation_signal == 2) then o(layer + 1)%onedarray(m) = & 1.0d0 / (1.0d0 + exp(- net(m))) else if (activation_signal == 3) then o(layer + 1)%onedarray(m) = net(m) end if ohat(layer + 1)%onedarray(m) = o(layer + 1)%onedarray(m) end do ohat(layer + 1)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2) + 1) = 1.0d0 deallocate(net) end do nn = size(o) - 2 allocate(D(nn + 1)) do layer = 1, nn + 1 allocate(D(layer)%onedarray(size(o(layer + 1)%onedarray))) do j = 1, size(o(layer + 1)%onedarray) if (activation_signal == 1) then D(layer)%onedarray(j) = (1.0d0 - & o(layer + 1)%onedarray(j)* o(layer + 1)%onedarray(j)) elseif (activation_signal == 2) then D(layer)%onedarray(j) = o(layer + 1)%onedarray(j) * & (1.0d0 - o(layer + 1)%onedarray(j)) elseif (activation_signal == 3) then D(layer)%onedarray(j) = 1.0d0 end if end do end do allocate(delta(nn + 1)) allocate(delta(nn + 1)%onedarray(1)) delta(nn + 1)%onedarray(1) = D(nn + 1)%onedarray(1) do layer = nn, 1, -1 allocate(delta(layer)%onedarray(size(D(layer)%onedarray))) do p = 1, size(D(layer)%onedarray) delta(layer)%onedarray(p) = 0.0d0 do q = 1, size(delta(layer + 1)%onedarray) temp1 = D(layer)%onedarray(p) * unraveled_parameters(& element)%weights(layer + 1)%twodarray(p, q) temp2 = temp1 * delta(layer + 1)%onedarray(q) delta(layer)%onedarray(p) = & delta(layer)%onedarray(p) + temp2 end do end do end do unraveled_daenergy_dparameters(element)%intercept = 1.0d0 unraveled_daenergy_dparameters(element)%slope = & o(nn + 2)%onedarray(1) do layer = 1, nn + 1 do p = 1, size(ohat(layer)%onedarray) do q = 1, size(delta(layer)%onedarray) unraveled_daenergy_dparameters(element)%weights(& layer)%twodarray(p, q) = & unraveled_parameters(element)%slope * & ohat(layer)%onedarray(p) * delta(layer)%onedarray(q) end do end do end do deallocate(hiddensizes) do p = 1, size(o) deallocate(o(p)%onedarray) end do deallocate(o) do p = 1, size(ohat) deallocate(ohat(p)%onedarray) end do deallocate(ohat) do p = 1, size(delta) deallocate(delta(p)%onedarray) end do deallocate(delta) do p = 1, size(D) deallocate(D(p)%onedarray) end do deallocate(D) ! changing the derivatives of the energy from derived-type ! form into vector k = 0 l = 0 do element = 1, num_elements if (element > 1) then k = k + no_layers_of_elements(element - 1) end if do j = 1, no_layers_of_elements(element) - 1 num_rows = no_nodes_of_elements(k + j) + 1 num_cols = no_nodes_of_elements(k + j + 1) do p = 1, num_rows do q = 1, num_cols calculate_datomicenergy_dparameters(& l + (p - 1) * num_cols + q) = & unraveled_daenergy_dparameters(& element)%weights(j)%twodarray(p, q) end do end do l = l + num_rows * num_cols end do end do do element = 1, num_elements calculate_datomicenergy_dparameters(l + 2 * element - 1) = & unraveled_daenergy_dparameters(element)%intercept calculate_datomicenergy_dparameters(l + 2 * element) = & unraveled_daenergy_dparameters(element)%slope end do ! deallocating derived-type parameters do element = 1, num_elements deallocate(unraveled_parameters(element)%weights) deallocate(unraveled_daenergy_dparameters(element)%weights) end do end function calculate_datomicenergy_dparameters !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Returns derivative of force w.r.t. parameters in the ! image-centered mode. function calculate_dforce_dparameters_(num_inputs, inputs, & inputs_, num_parameters, parameters) implicit none integer:: num_inputs, num_parameters double precision:: calculate_dforce_dparameters_(num_parameters) double precision:: parameters(num_parameters) double precision:: inputs(num_inputs) double precision:: inputs_(num_inputs) integer:: m, n, j, l, layer, p, q, nn, num_cols integer:: num_rows double precision:: temp1, temp2 integer, allocatable:: hiddensizes(:) double precision, allocatable:: net(:) type(real_one_d_array), allocatable:: o(:), ohat(:) type(real_one_d_array), allocatable:: delta(:), D(:) type(real_one_d_array), allocatable:: doutputs_dinputs(:) double precision, allocatable:: dohat_dinputs(:) type(real_one_d_array), allocatable:: dD_dinputs(:) type (real_one_d_array), allocatable:: ddelta_dinputs(:) double precision, allocatable:: & doutput_dinputsdweights(:, :) double precision, allocatable:: temp(:), temp3(:), temp4(:) double precision, allocatable:: temp5(:), temp6(:) type(real_two_d_array), allocatable:: weights(:) double precision:: intercept double precision:: slope type(real_two_d_array), allocatable:: unraveled_dforce_dweights(:) double precision:: dforce_dintercept double precision:: dforce_dslope ! changing the form of parameters from vector into derived-types l = 0 allocate(weights(no_layers_of_elements(1)-1)) do j = 1, no_layers_of_elements(1) - 1 num_rows = no_nodes_of_elements(j) + 1 num_cols = no_nodes_of_elements(j + 1) allocate(weights(j)%twodarray(num_rows, num_cols)) do p = 1, num_rows do q = 1, num_cols weights(j)%twodarray(p, q) = & parameters(l + (p - 1) * num_cols + q) end do end do l = l + num_rows * num_cols end do intercept = parameters(l + 1) slope = parameters(l + 2) dforce_dintercept = 0.d0 dforce_dslope = 0.d0 l = 0 allocate(unraveled_dforce_dweights(no_layers_of_elements(1)-1)) do j = 1, no_layers_of_elements(1) - 1 num_rows = no_nodes_of_elements(j) + 1 num_cols = no_nodes_of_elements(j + 1) allocate(unraveled_dforce_dweights(j)%twodarray(num_rows, & num_cols)) do p = 1, num_rows do q = 1, num_cols unraveled_dforce_dweights(j)%twodarray(p, q) = 0.0d0 end do end do l = l + num_rows * num_cols end do allocate(hiddensizes(no_layers_of_elements(1) - 2)) do m = 1, no_layers_of_elements(1) - 2 hiddensizes(m) = no_nodes_of_elements(m + 1) end do allocate(o(no_layers_of_elements(1))) allocate(ohat(no_layers_of_elements(1))) layer = 1 allocate(o(1)%onedarray(num_inputs)) allocate(ohat(1)%onedarray(num_inputs + 1)) do m = 1, num_inputs o(1)%onedarray(m) = inputs(m) end do do layer = 1, size(hiddensizes) + 1 do m = 1, size(weights(layer)%twodarray, dim=1) - 1 ohat(layer)%onedarray(m) = o(layer)%onedarray(m) end do ohat(layer)%onedarray(& size(weights(layer)%twodarray, dim=1)) = 1.0d0 allocate(net(size(weights(layer)%twodarray, dim=2))) allocate(o(layer + 1)%onedarray(& size(weights(layer)%twodarray, dim=2))) allocate(ohat(layer + 1)%onedarray(& size(weights(layer)%twodarray, dim=2) + 1)) do m = 1, size(weights(layer)%twodarray, dim=2) net(m) = 0.0d0 do n = 1, size(weights(layer)%twodarray, dim=1) net(m) = net(m) + & ohat(layer)%onedarray(n) * & weights(layer)%twodarray(n, m) end do if (activation_signal == 1) then o(layer + 1)%onedarray(m) = tanh(net(m)) else if (activation_signal == 2) then o(layer + 1)%onedarray(m) = & 1.0d0 / (1.0d0 + exp(- net(m))) else if (activation_signal == 3) then o(layer + 1)%onedarray(m) = net(m) end if ohat(layer + 1)%onedarray(m) = o(layer + 1)%onedarray(m) end do ohat(layer + 1)%onedarray(& size(weights(layer)%twodarray, dim=2) + 1) = 1.0d0 deallocate(net) end do nn = size(o) - 2 allocate(D(nn + 1)) do layer = 1, nn + 1 allocate(D(layer)%onedarray(size(o(layer + 1)%onedarray))) do j = 1, size(o(layer + 1)%onedarray) if (activation_signal == 1) then D(layer)%onedarray(j) = (1.0d0 - & o(layer + 1)%onedarray(j)* o(layer + 1)%onedarray(j)) elseif (activation_signal == 2) then D(layer)%onedarray(j) = o(layer + 1)%onedarray(j) * & (1.0d0 - o(layer + 1)%onedarray(j)) elseif (activation_signal == 3) then D(layer)%onedarray(j) = 1.0d0 end if end do end do allocate(delta(nn + 1)) allocate(delta(nn + 1)%onedarray(1)) delta(nn + 1)%onedarray(1) = D(nn + 1)%onedarray(1) do layer = nn, 1, -1 allocate(delta(layer)%onedarray(size(D(layer)%onedarray))) do p = 1, size(D(layer)%onedarray) delta(layer)%onedarray(p) = 0.0d0 do q = 1, size(delta(layer + 1)%onedarray) temp1 = D(layer)%onedarray(p) * weights(& layer + 1)%twodarray(p, q) temp2 = temp1 * delta(layer + 1)%onedarray(q) delta(layer)%onedarray(p) = & delta(layer)%onedarray(p) + temp2 end do end do end do allocate(doutputs_dinputs(nn + 2)) allocate(doutputs_dinputs(1)%onedarray(num_inputs)) do m = 1, num_inputs doutputs_dinputs(1)%onedarray(m) = inputs_(m) end do do layer = 1, nn + 1 allocate(temp(size(weights(layer)%twodarray, dim = 2))) do p = 1, size(weights(layer)%twodarray, dim = 2) temp(p) = 0.0d0 do q = 1, size(weights(layer)%twodarray, dim = 1) - 1 temp(p) = temp(p) + doutputs_dinputs(& layer)%onedarray(q) * weights(layer)%twodarray(q, p) end do end do q = size(o(layer + 1)%onedarray) allocate(doutputs_dinputs(layer + 1)%onedarray(q)) do p = 1, size(o(layer + 1)%onedarray) if (activation_signal == 1) then doutputs_dinputs(layer + 1)%onedarray(p) = temp(p) * & (1.0d0 - o(layer + 1)%onedarray(p) * & o(layer + 1)%onedarray(p)) else if (activation_signal == 2) then doutputs_dinputs(layer + 1)%onedarray(p) = & temp(p) * (1.0d0 - o(layer + 1)%onedarray(p)) * & o(layer + 1)%onedarray(p) else if (activation_signal == 3) then doutputs_dinputs(layer+ 1)%onedarray(p) = temp(p) end if end do deallocate(temp) end do allocate(dD_dinputs(nn + 1)) do layer = 1, nn + 1 allocate(dD_dinputs(layer)%onedarray(& size(o(layer + 1)%onedarray))) do p = 1, size(o(layer + 1)%onedarray) if (activation_signal == 1) then dD_dinputs(layer)%onedarray(p) = & - 2.0d0 * o(layer + 1)%onedarray(p) * & doutputs_dinputs(layer + 1)%onedarray(p) elseif (activation_signal == 2) then dD_dinputs(layer)%onedarray(p) = & doutputs_dinputs(layer + 1)%onedarray(p) * & (1.0d0 - 2.0d0 * o(layer + 1)%onedarray(p)) elseif (activation_signal == 3) then dD_dinputs(layer)%onedarray(p) =0.0d0 end if end do end do allocate(ddelta_dinputs(nn + 1)) allocate(ddelta_dinputs(nn + 1)%onedarray(1)) ddelta_dinputs(nn + 1)%onedarray(1) = & dD_dinputs(nn + 1)%onedarray(1) do layer = nn, 1, -1 allocate(temp3(& size(weights(layer + 1)%twodarray, dim = 1) - 1)) allocate(temp4(& size(weights(layer + 1)%twodarray, dim = 1) - 1)) do p = 1, size(weights(layer + 1)%twodarray, dim = 1) - 1 temp3(p) = 0.0d0 temp4(p) = 0.0d0 do q = 1, size(delta(layer + 1)%onedarray) temp3(p) = temp3(p) + weights(layer + 1)%twodarray(& p, q) * delta(layer + 1)%onedarray(q) temp4(p) = temp4(p) + weights(layer + 1)%twodarray(& p, q) * ddelta_dinputs(layer + 1)%onedarray(q) end do end do allocate(temp5(size(dD_dinputs(layer)%onedarray))) allocate(temp6(size(dD_dinputs(layer)%onedarray))) allocate(ddelta_dinputs(layer)%onedarray(& size(dD_dinputs(layer)%onedarray))) do p = 1, size(dD_dinputs(layer)%onedarray) temp5(p) = & dD_dinputs(layer)%onedarray(p) * temp3(p) temp6(p) = D(layer)%onedarray(p) * temp4(p) ddelta_dinputs(layer)%onedarray(p)= & temp5(p) + temp6(p) end do deallocate(temp3) deallocate(temp4) deallocate(temp5) deallocate(temp6) end do dforce_dslope = doutputs_dinputs(nn + 2)%onedarray(1) ! force is multiplied by -1, because it is -dE/dx and not dE/dx. dforce_dslope = -1.0d0 * dforce_dslope do layer = 1, nn + 1 allocate(dohat_dinputs(& size(doutputs_dinputs(layer)%onedarray) + 1)) do p = 1, size(doutputs_dinputs(layer)%onedarray) dohat_dinputs(p) = & doutputs_dinputs(layer)%onedarray(p) end do dohat_dinputs(& size(doutputs_dinputs(layer)%onedarray) + 1) = 0.0d0 allocate(doutput_dinputsdweights(& size(dohat_dinputs), size(delta(layer)%onedarray))) do p = 1, size(dohat_dinputs) do q = 1, size(delta(layer)%onedarray) doutput_dinputsdweights(p, q)= 0.0d0 end do end do do p = 1, size(dohat_dinputs) do q = 1, size(delta(layer)%onedarray) doutput_dinputsdweights(p, q) = & doutput_dinputsdweights(p, q) + & dohat_dinputs(p) * delta(layer)%onedarray(q) + & ohat(layer)%onedarray(p)* & ddelta_dinputs(layer)%onedarray(q) end do end do do p = 1, size(ohat(layer)%onedarray) do q = 1, size(delta(layer)%onedarray) unraveled_dforce_dweights(layer)%twodarray(p, q) = & slope * doutput_dinputsdweights(p, q) ! force is multiplied by -1, because it is -dE/dx and ! not dE/dx. unraveled_dforce_dweights(layer)%twodarray(p, q) = & -1.0d0 * unraveled_dforce_dweights(layer)%twodarray(p, q) end do end do deallocate(dohat_dinputs) deallocate(doutput_dinputsdweights) end do ! deallocating neural network deallocate(hiddensizes) do p = 1, size(o) deallocate(o(p)%onedarray) end do deallocate(o) do p = 1, size(ohat) deallocate(ohat(p)%onedarray) end do deallocate(ohat) do p = 1, size(delta) deallocate(delta(p)%onedarray) end do deallocate(delta) do p = 1, size(D) deallocate(D(p)%onedarray) end do deallocate(D) do p = 1, size(doutputs_dinputs) deallocate(doutputs_dinputs(p)%onedarray) end do deallocate(doutputs_dinputs) do p = 1, size(ddelta_dinputs) deallocate(ddelta_dinputs(p)%onedarray) end do deallocate(ddelta_dinputs) do p = 1, size(dD_dinputs) deallocate(dD_dinputs(p)%onedarray) end do deallocate(dD_dinputs) l = 0 do j = 1, no_layers_of_elements(1) - 1 num_rows = no_nodes_of_elements(j) + 1 num_cols = no_nodes_of_elements(j + 1) do p = 1, num_rows do q = 1, num_cols calculate_dforce_dparameters_(& l + (p - 1) * num_cols + q) = & unraveled_dforce_dweights(j)%twodarray(p, q) end do end do l = l + num_rows * num_cols end do calculate_dforce_dparameters_(l + 1) = dforce_dintercept calculate_dforce_dparameters_(l + 2) = dforce_dslope ! deallocating derived-type parameters do p = 1, size(weights) deallocate(weights(p)%twodarray) end do deallocate(weights) do p = 1, size(unraveled_dforce_dweights) deallocate(unraveled_dforce_dweights(p)%twodarray) end do deallocate(unraveled_dforce_dweights) end function calculate_dforce_dparameters_ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Returns derivative of force w.r.t. parameters in the ! atom-centered mode function calculate_dforce_dparameters(symbol, len_of_fingerprint, & fingerprint, fingerprintprime, num_elements, elements_numbers, & num_parameters, parameters) implicit none integer:: symbol, len_of_fingerprint integer:: num_parameters, num_elements double precision:: fingerprint(len_of_fingerprint) double precision:: fingerprintprime(len_of_fingerprint) integer:: elements_numbers(num_elements) double precision:: parameters(num_parameters) double precision:: calculate_dforce_dparameters(num_parameters) integer:: element, m, n, j, k, l, layer, p, q, nn, num_cols integer:: num_rows double precision:: temp1, temp2 integer, allocatable:: hiddensizes(:) double precision, allocatable:: net(:) type(real_one_d_array), allocatable:: o(:), ohat(:) type(real_one_d_array), allocatable:: delta(:), D(:) type(real_one_d_array), allocatable:: doutputs_dinputs(:) double precision, allocatable:: dohat_dinputs(:) type(real_one_d_array), allocatable:: dD_dinputs(:) type (real_one_d_array), allocatable:: ddelta_dinputs(:) double precision, allocatable:: & doutput_dinputsdweights(:, :) double precision, allocatable:: temp(:), temp3(:), temp4(:) double precision, allocatable:: temp5(:), temp6(:) type(element_parameters):: unraveled_parameters(num_elements) type(element_parameters):: & unraveled_dforce_dparameters(num_elements) double precision:: fingerprint_(len_of_fingerprint) double precision:: fingerprintprime_(len_of_fingerprint) ! scaling fingerprints do element = 1, num_elements if (symbol == & elements_numbers(element)) then exit end if end do do l = 1, len_of_fingerprint if ((max_fingerprints(element, l) - & min_fingerprints(element, l)) .GT. & (10.0d0 ** (-8.0d0))) then fingerprint_(l) = -1.0d0 + 2.0d0 * & (fingerprint(l) - min_fingerprints(element, l)) / & (max_fingerprints(element, l) - & min_fingerprints(element, l)) else fingerprint_(l) = fingerprint(l) endif end do ! scaling fingerprintprimes do l = 1, len_of_fingerprint if ((max_fingerprints(element, l) - & min_fingerprints(element, l)) .GT. & (10.0d0 ** (-8.0d0))) then fingerprintprime_(l) = & 2.0d0 * fingerprintprime(l) / & (max_fingerprints(element, l) - & min_fingerprints(element, l)) else fingerprintprime_(l) = fingerprintprime(l) endif end do ! changing the form of parameters from vector into derived-types k = 0 l = 0 do element = 1, num_elements allocate(unraveled_parameters(element)%weights(& no_layers_of_elements(element)-1)) if (element .GT. 1) then k = k + no_layers_of_elements(element - 1) end if do j = 1, no_layers_of_elements(element) - 1 num_rows = no_nodes_of_elements(k + j) + 1 num_cols = no_nodes_of_elements(k + j + 1) allocate(unraveled_parameters(& element)%weights(j)%twodarray(num_rows, num_cols)) do p = 1, num_rows do q = 1, num_cols unraveled_parameters(element)%weights(j)%twodarray(& p, q) = parameters(l + (p - 1) * num_cols + q) end do end do l = l + num_rows * num_cols end do end do do element = 1, num_elements unraveled_parameters(element)%intercept = & parameters(l + 2 * element - 1) unraveled_parameters(element)%slope = & parameters(l + 2 * element) end do do element = 1, num_elements unraveled_dforce_dparameters(element)%intercept = 0.d0 unraveled_dforce_dparameters(element)%slope = 0.d0 end do k = 0 l = 0 do element = 1, num_elements allocate(unraveled_dforce_dparameters(element)%weights(& no_layers_of_elements(element)-1)) if (element > 1) then k = k + no_layers_of_elements(element - 1) end if do j = 1, no_layers_of_elements(element) - 1 num_rows = no_nodes_of_elements(k + j) + 1 num_cols = no_nodes_of_elements(k + j + 1) allocate(unraveled_dforce_dparameters(& element)%weights(j)%twodarray(num_rows, num_cols)) do p = 1, num_rows do q = 1, num_cols unraveled_dforce_dparameters(& element)%weights(j)%twodarray(p, q) = 0.0d0 end do end do l = l + num_rows * num_cols end do end do p = 0 do element = 1, num_elements if (symbol == elements_numbers(element)) then exit else p = p + no_layers_of_elements(element) end if end do allocate(hiddensizes(no_layers_of_elements(element) - 2)) do m = 1, no_layers_of_elements(element) - 2 hiddensizes(m) = no_nodes_of_elements(p + m + 1) end do allocate(o(no_layers_of_elements(element))) allocate(ohat(no_layers_of_elements(element))) layer = 1 allocate(o(1)%onedarray(len_of_fingerprint)) allocate(ohat(1)%onedarray(len_of_fingerprint + 1)) do m = 1, len_of_fingerprint o(1)%onedarray(m) = fingerprint_(m) end do do layer = 1, size(hiddensizes) + 1 do m = 1, size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=1) - 1 ohat(layer)%onedarray(m) = o(layer)%onedarray(m) end do ohat(layer)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=1)) = 1.0d0 allocate(net(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2))) allocate(o(layer + 1)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2))) allocate(ohat(layer + 1)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2) + 1)) do m = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim=2) net(m) = 0.0d0 do n = 1, size(unraveled_parameters(element)%weights(& layer)%twodarray, dim=1) net(m) = net(m) + & ohat(layer)%onedarray(n) * & unraveled_parameters(element)%weights(& layer)%twodarray(n, m) end do if (activation_signal == 1) then o(layer + 1)%onedarray(m) = tanh(net(m)) else if (activation_signal == 2) then o(layer + 1)%onedarray(m) = & 1.0d0 / (1.0d0 + exp(- net(m))) else if (activation_signal == 3) then o(layer + 1)%onedarray(m) = net(m) end if ohat(layer + 1)%onedarray(m) = o(layer + 1)%onedarray(m) end do ohat(layer + 1)%onedarray(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim=2) + 1) = 1.0d0 deallocate(net) end do nn = size(o) - 2 allocate(D(nn + 1)) do layer = 1, nn + 1 allocate(D(layer)%onedarray(size(o(layer + 1)%onedarray))) do j = 1, size(o(layer + 1)%onedarray) if (activation_signal == 1) then D(layer)%onedarray(j) = & (1.0d0 - o(layer + 1)%onedarray(j)* & o(layer + 1)%onedarray(j)) elseif (activation_signal == 2) then D(layer)%onedarray(j) = o(layer + 1)%onedarray(j) * & (1.0d0 - o(layer + 1)%onedarray(j)) elseif (activation_signal == 3) then D(layer)%onedarray(j) = 1.0d0 end if end do end do allocate(delta(nn + 1)) allocate(delta(nn + 1)%onedarray(1)) delta(nn + 1)%onedarray(1) = D(nn + 1)%onedarray(1) do layer = nn, 1, -1 allocate(delta(layer)%onedarray(size(D(layer)%onedarray))) do p = 1, size(D(layer)%onedarray) delta(layer)%onedarray(p) = 0.0d0 do q = 1, size(delta(layer + 1)%onedarray) temp1 = D(layer)%onedarray(p) * & unraveled_parameters(element)%weights(& layer + 1)%twodarray(p, q) temp2 = temp1 * delta(layer + 1)%onedarray(q) delta(layer)%onedarray(p) = & delta(layer)%onedarray(p) + temp2 end do end do end do allocate(doutputs_dinputs(nn + 2)) allocate(doutputs_dinputs(1)%onedarray(& len_of_fingerprint)) do m = 1, len_of_fingerprint doutputs_dinputs(1)%onedarray(m) = fingerprintprime_(m) end do do layer = 1, nn + 1 allocate(temp(size(unraveled_parameters(& element)%weights(layer)%twodarray, dim = 2))) do p = 1, size(unraveled_parameters(& element)%weights(layer)%twodarray, dim = 2) temp(p) = 0.0d0 do q = 1, size(unraveled_parameters(& element)%weights(layer)%twodarray, dim = 1) - 1 temp(p) = temp(p) + doutputs_dinputs(& layer)%onedarray(q) * unraveled_parameters(& element)%weights(layer)%twodarray(q, p) end do end do q = size(o(layer + 1)%onedarray) allocate(doutputs_dinputs(layer + 1)%onedarray(q)) do p = 1, size(o(layer + 1)%onedarray) if (activation_signal == 1) then doutputs_dinputs(layer + 1)%onedarray(p) = temp(p) * & (1.0d0 - o(layer + 1)%onedarray(p) * & o(layer + 1)%onedarray(p)) else if (activation_signal == 2) then doutputs_dinputs(layer + 1)%onedarray(p) = temp(p) * & (1.0d0 - o(layer + 1)%onedarray(p)) * & o(layer + 1)%onedarray(p) else if (activation_signal == 3) then doutputs_dinputs(layer+ 1)%onedarray(p) = temp(p) end if end do deallocate(temp) end do allocate(dD_dinputs(nn + 1)) do layer = 1, nn + 1 allocate(dD_dinputs(layer)%onedarray(& size(o(layer + 1)%onedarray))) do p = 1, size(o(layer + 1)%onedarray) if (activation_signal == 1) then dD_dinputs(layer)%onedarray(p) =- 2.0d0 * & o(layer + 1)%onedarray(p) * & doutputs_dinputs(layer + 1)%onedarray(p) elseif (activation_signal == 2) then dD_dinputs(layer)%onedarray(p) = & doutputs_dinputs(layer + 1)%onedarray(p) * & (1.0d0 - 2.0d0 * o(layer + 1)%onedarray(p)) elseif (activation_signal == 3) then dD_dinputs(layer)%onedarray(p) =0.0d0 end if end do end do allocate(ddelta_dinputs(nn + 1)) allocate(ddelta_dinputs(nn + 1)%onedarray(1)) ddelta_dinputs(nn + 1)%onedarray(1) = & dD_dinputs(nn + 1)%onedarray(1) do layer = nn, 1, -1 allocate(temp3(size(unraveled_parameters(element)%weights(& layer + 1)%twodarray, dim = 1) - 1)) allocate(temp4(size(unraveled_parameters(element)%weights(& layer + 1)%twodarray, dim = 1) - 1)) do p = 1, size(unraveled_parameters(element)%weights(& layer + 1)%twodarray, dim = 1) - 1 temp3(p) = 0.0d0 temp4(p) = 0.0d0 do q = 1, size(delta(layer + 1)%onedarray) temp3(p) = temp3(p) + unraveled_parameters(& element)%weights(layer + 1)%twodarray(p, q) * & delta(layer + 1)%onedarray(q) temp4(p) = temp4(p) + unraveled_parameters(& element)%weights(layer + 1)%twodarray(p, q) * & ddelta_dinputs(layer + 1)%onedarray(q) end do end do allocate(temp5(size(dD_dinputs(layer)%onedarray))) allocate(temp6(size(dD_dinputs(layer)%onedarray))) allocate(ddelta_dinputs(layer)%onedarray(& size(dD_dinputs(layer)%onedarray))) do p = 1, size(dD_dinputs(layer)%onedarray) temp5(p) = & dD_dinputs(layer)%onedarray(p) * temp3(p) temp6(p) = D(layer)%onedarray(p) * temp4(p) ddelta_dinputs(layer)%onedarray(p)= & temp5(p) + temp6(p) end do deallocate(temp3) deallocate(temp4) deallocate(temp5) deallocate(temp6) end do unraveled_dforce_dparameters(element)%slope = & doutputs_dinputs(nn + 2)%onedarray(1) ! force is multiplied by -1, because it is -dE/dx and not dE/dx. unraveled_dforce_dparameters(element)%slope = & -1.0d0 * unraveled_dforce_dparameters(element)%slope do layer = 1, nn + 1 allocate(dohat_dinputs(& size(doutputs_dinputs(layer)%onedarray) + 1)) do p = 1, size(doutputs_dinputs(layer)%onedarray) dohat_dinputs(p) = & doutputs_dinputs(layer)%onedarray(p) end do dohat_dinputs(& size(doutputs_dinputs(layer)%onedarray) + 1) = 0.0d0 allocate(doutput_dinputsdweights(& size(dohat_dinputs), size(delta(layer)%onedarray))) do p = 1, size(dohat_dinputs) do q = 1, size(delta(layer)%onedarray) doutput_dinputsdweights(p, q)= 0.0d0 end do end do do p = 1, size(dohat_dinputs) do q = 1, size(delta(layer)%onedarray) doutput_dinputsdweights(p, q) = & doutput_dinputsdweights(p, q) + & dohat_dinputs(p) * delta(layer)%onedarray(q) + & ohat(layer)%onedarray(p)* & ddelta_dinputs(layer)%onedarray(q) end do end do do p = 1, size(ohat(layer)%onedarray) do q = 1, size(delta(layer)%onedarray) unraveled_dforce_dparameters(element)%weights(& layer)%twodarray(p, q) = & unraveled_parameters(element)%slope * & doutput_dinputsdweights(p, q) ! force is multiplied by -1, because it is -dE/dx and ! not dE/dx. unraveled_dforce_dparameters(element)%weights(& layer)%twodarray(p, q) = & -1.0d0 * unraveled_dforce_dparameters(element)%weights(& layer)%twodarray(p, q) end do end do deallocate(dohat_dinputs) deallocate(doutput_dinputsdweights) end do ! deallocating neural network deallocate(hiddensizes) do p = 1, size(o) deallocate(o(p)%onedarray) end do deallocate(o) do p = 1, size(ohat) deallocate(ohat(p)%onedarray) end do deallocate(ohat) do p = 1, size(delta) deallocate(delta(p)%onedarray) end do deallocate(delta) do p = 1, size(D) deallocate(D(p)%onedarray) end do deallocate(D) do p = 1, size(doutputs_dinputs) deallocate(doutputs_dinputs(p)%onedarray) end do deallocate(doutputs_dinputs) do p = 1, size(ddelta_dinputs) deallocate(ddelta_dinputs(p)%onedarray) end do deallocate(ddelta_dinputs) do p = 1, size(dD_dinputs) deallocate(dD_dinputs(p)%onedarray) end do deallocate(dD_dinputs) k = 0 l = 0 do element = 1, num_elements if (element > 1) then k = k + no_layers_of_elements(element - 1) end if do j = 1, no_layers_of_elements(element) - 1 num_rows = no_nodes_of_elements(k + j) + 1 num_cols = no_nodes_of_elements(k + j + 1) do p = 1, num_rows do q = 1, num_cols calculate_dforce_dparameters(& l + (p - 1) * num_cols + q) = & unraveled_dforce_dparameters(& element)%weights(j)%twodarray(p, q) end do end do l = l + num_rows * num_cols end do end do do element = 1, num_elements calculate_dforce_dparameters(l + 2 * element - 1) = & unraveled_dforce_dparameters(element)%intercept calculate_dforce_dparameters(l + 2 * element) = & unraveled_dforce_dparameters(element)%slope end do ! deallocating derived-type parameters do element = 1, num_elements deallocate(unraveled_parameters(element)%weights) deallocate(unraveled_dforce_dparameters(element)%weights) end do end function calculate_dforce_dparameters !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! end module neuralnetwork !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! amp-0.6/amp/analysis.py0000644000175000017500000006443513137634440015007 0ustar muammarmuammar#!/usr/bin/env python from . import Amp from .utilities import now, hash_images, make_filename import os import numpy as np from matplotlib import pyplot from matplotlib.backends.backend_pdf import PdfPages from matplotlib import rcParams rcParams.update({'figure.autolayout': True}) def plot_sensitivity(load, images, d=0.0001, label='sensitivity', dblabel=None, plotfile=None, overwrite=False, energy_coefficient=1.0, force_coefficient=0.04): """Returns the plot of loss function in terms of perturbed parameters. Takes the load file and images. Any other keyword taken by the Amp calculator can be fed to this class also. Parameters ---------- load : str Path for loading an existing ".amp" file. Should be fed like 'load="filename.amp"'. images : list or str List of ASE atoms objects with positions, symbols, energies, and forces in ASE format. This can also be the path to an ASE trajectory (.traj) or database (.db) file. Energies can be obtained from any reference, e.g. DFT calculations. d : float The amount of perturbation in each parameter. label : str Default prefix/location used for all files. dblabel : str Optional separate prefix/location of database files, including fingerprints, fingerprint primes, and neighborlists, to avoid calculating them. If not supplied, just uses the value from label. plotfile : Object File for the plot. overwrite : bool If a plot or an script containing values found overwrite it. energy_coefficient : float Coefficient of energy loss in the total loss function. force_coefficient : float Coefficient of force loss in the total loss function. """ from amp.model import LossFunction calc = Amp.load(file=load) if plotfile is None: plotfile = make_filename(label, '-plot.pdf') if (not overwrite) and os.path.exists(plotfile): raise IOError('File exists: %s.\nIf you want to overwrite,' ' set overwrite=True or manually delete.' % plotfile) calc.dblabel = label if dblabel is None else dblabel if force_coefficient == 0.: calculate_derivatives = False else: calculate_derivatives = True calc._log('\nAmp sensitivity analysis started. ' + now() + '\n') calc._log('Descriptor: %s' % calc.descriptor.__class__.__name__) calc._log('Model: %s' % calc.model.__class__.__name__) images = hash_images(images) calc._log('\nDescriptor\n==========') calc.descriptor.calculate_fingerprints( images=images, parallel=calc._parallel, log=calc._log, calculate_derivatives=calculate_derivatives) vector = calc.model.vector.copy() lossfunction = LossFunction(energy_coefficient=energy_coefficient, force_coefficient=force_coefficient, parallel=calc._parallel, ) calc.model.lossfunction = lossfunction # Set up local loss function. calc.model.lossfunction.attach_model( calc.model, fingerprints=calc.descriptor.fingerprints, fingerprintprimes=calc.descriptor.fingerprintprimes, images=images) originalloss = calc.model.lossfunction.get_loss( vector, lossprime=False)['loss'] calc._log('\n Perturbing parameters...', tic='perturb') allparameters = [] alllosses = [] num_parameters = len(vector) for count in range(num_parameters): calc._log('parameter %i out of %i' % (count + 1, num_parameters)) parameters = [] losses = [] # parameter is perturbed -d and loss function calculated. vector[count] -= d parameters.append(vector[count]) perturbedloss = calc.model.lossfunction.get_loss( vector, lossprime=False)['loss'] losses.append(perturbedloss) vector[count] += d parameters.append(vector[count]) losses.append(originalloss) # parameter is perturbed +d and loss function calculated. vector[count] += d parameters.append(vector[count]) perturbedloss = calc.model.lossfunction.get_loss( vector, lossprime=False)['loss'] losses.append(perturbedloss) allparameters.append(parameters) alllosses.append(losses) # returning back to the original value. vector[count] -= d calc._log('...parameters perturbed and loss functions calculated', toc='perturb') calc._log('Plotting loss function vs perturbed parameters...', tic='plot') with PdfPages(plotfile) as pdf: count = 0 for parameter in vector: fig = pyplot.figure() ax = fig.add_subplot(111) ax.plot(allparameters[count], alllosses[count], marker='o', linestyle='--', color='b',) xmin = allparameters[count][0] - \ 0.1 * (allparameters[count][-1] - allparameters[count][0]) xmax = allparameters[count][-1] + \ 0.1 * (allparameters[count][-1] - allparameters[count][0]) ymin = min(alllosses[count]) - \ 0.1 * (max(alllosses[count]) - min(alllosses[count])) ymax = max(alllosses[count]) + \ 0.1 * (max(alllosses[count]) - min(alllosses[count])) ax.set_xlim([xmin, xmax]) ax.set_ylim([ymin, ymax]) ax.set_xlabel('parameter no %i' % count) ax.set_ylabel('loss function') pdf.savefig(fig) pyplot.close(fig) count += 1 calc._log(' ...loss functions plotted.', toc='plot') def plot_parity(load, images, label='parity', dblabel=None, plot_forces=True, plotfile=None, color='b.', overwrite=False, returndata=False, energy_coefficient=1.0, force_coefficient=0.04): """Makes a parity plot of Amp energies and forces versus real energies and forces. Parameters ---------- load : str Path for loading an existing ".amp" file. Should be fed like 'load="filename.amp"'. images : list or str List of ASE atoms objects with positions, symbols, energies, and forces in ASE format. This can also be the path to an ASE trajectory (.traj) or database (.db) file. Energies can be obtained from any reference, e.g. DFT calculations. label : str Default prefix/location used for all files. dblabel : str Optional separate prefix/location of database files, including fingerprints, fingerprint primes, and neighborlists, to avoid calculating them. If not supplied, just uses the value from label. plot_forces : bool Determines whether or not forces should be plotted as well. plotfile : Object File for the plot. color : str Plot color. overwrite : bool If a plot or an script containing values found overwrite it. returndata : bool Whether to return a reference to the figures and their data or not. energy_coefficient : float Coefficient of energy loss in the total loss function. force_coefficient : float Coefficient of force loss in the total loss function. """ calc = Amp.load(file=load, label=label, dblabel=dblabel) if plotfile is None: plotfile = make_filename(label, '-plot.pdf') if (not overwrite) and os.path.exists(plotfile): raise IOError('File exists: %s.\nIf you want to overwrite,' ' set overwrite=True or manually delete.' % plotfile) if (force_coefficient != 0.) or (plot_forces is True): calculate_derivatives = True else: calculate_derivatives = False calc._log('\nAmp parity plot started. ' + now() + '\n') calc._log('Descriptor: %s' % calc.descriptor.__class__.__name__) calc._log('Model: %s' % calc.model.__class__.__name__) images = hash_images(images, log=calc._log) calc._log('\nDescriptor\n==========') calc.descriptor.calculate_fingerprints( images=images, parallel=calc._parallel, log=calc._log, calculate_derivatives=calculate_derivatives) calc._log('Calculating potential energies...', tic='pot-energy') energy_data = {} for hash, image in images.iteritems(): amp_energy = calc.model.calculate_energy( calc.descriptor.fingerprints[hash]) actual_energy = image.get_potential_energy(apply_constraint=False) energy_data[hash] = [actual_energy, amp_energy] calc._log('...potential energies calculated.', toc='pot-energy') min_act_energy = min([energy_data[hash][0] for hash, image in images.iteritems()]) max_act_energy = max([energy_data[hash][0] for hash, image in images.iteritems()]) if plot_forces is False: fig = pyplot.figure(figsize=(5., 5.)) ax = fig.add_subplot(111) else: fig = pyplot.figure(figsize=(5., 10.)) ax = fig.add_subplot(211) calc._log('Plotting energies...', tic='energy-plot') for hash, image in images.iteritems(): ax.plot(energy_data[hash][0], energy_data[hash][1], color) # draw line ax.plot([min_act_energy, max_act_energy], [min_act_energy, max_act_energy], 'r-', lw=0.3,) ax.set_xlabel("ab initio energy, eV") ax.set_ylabel("Amp energy, eV") ax.set_title("Energies") calc._log('...energies plotted.', toc='energy-plot') if plot_forces is True: ax = fig.add_subplot(212) calc._log('Calculating forces...', tic='forces') force_data = {} for hash, image in images.iteritems(): amp_forces = \ calc.model.calculate_forces( calc.descriptor.fingerprints[hash], calc.descriptor.fingerprintprimes[hash]) actual_forces = image.get_forces(apply_constraint=False) force_data[hash] = [actual_forces, amp_forces] calc._log('...forces calculated.', toc='forces') min_act_force = min([force_data[hash][0][index][k] for hash, image in images.iteritems() for index in range(len(image)) for k in range(3)]) max_act_force = max([force_data[hash][0][index][k] for hash, image in images.iteritems() for index in range(len(image)) for k in range(3)]) calc._log('Plotting forces...', tic='force-plot') for hash, image in images.iteritems(): for index in range(len(image)): for k in range(3): ax.plot(force_data[hash][0][index][k], force_data[hash][1][index][k], color) # draw line ax.plot([min_act_force, max_act_force], [min_act_force, max_act_force], 'r-', lw=0.3,) ax.set_xlabel("ab initio force, eV/Ang") ax.set_ylabel("Amp force, eV/Ang") ax.set_title("Forces") calc._log('...forces plotted.', toc='force-plot') fig.savefig(plotfile) if returndata: if plot_forces is False: return fig, energy_data else: return fig, energy_data, force_data def plot_error(load, images, label='error', dblabel=None, plot_forces=True, plotfile=None, color='b.', overwrite=False, returndata=False, energy_coefficient=1.0, force_coefficient=0.04): """Makes an error plot of Amp energies and forces versus real energies and forces. Parameters ---------- load : str Path for loading an existing ".amp" file. Should be fed like 'load="filename.amp"'. images : list or str List of ASE atoms objects with positions, symbols, energies, and forces in ASE format. This can also be the path to an ASE trajectory (.traj) or database (.db) file. Energies can be obtained from any reference, e.g. DFT calculations. label : str Default prefix/location used for all files. dblabel : str Optional separate prefix/location of database files, including fingerprints, fingerprint primes, and neighborlists, to avoid calculating them. If not supplied, just uses the value from label. plot_forces : bool Determines whether or not forces should be plotted as well. plotfile : Object File for the plot. color : str Plot color. overwrite : bool If a plot or an script containing values found overwrite it. returndata : bool Whether to return a reference to the figures and their data or not. energy_coefficient : float Coefficient of energy loss in the total loss function. force_coefficient : float Coefficient of force loss in the total loss function. """ calc = Amp.load(file=load) if plotfile is None: plotfile = make_filename(label, '-plot.pdf') if (not overwrite) and os.path.exists(plotfile): raise IOError('File exists: %s.\nIf you want to overwrite,' ' set overwrite=True or manually delete.' % plotfile) calc.dblabel = label if dblabel is None else dblabel if (force_coefficient != 0.) or (plot_forces is True): calculate_derivatives = True else: calculate_derivatives = False calc._log('\nAmp error plot started. ' + now() + '\n') calc._log('Descriptor: %s' % calc.descriptor.__class__.__name__) calc._log('Model: %s' % calc.model.__class__.__name__) images = hash_images(images, log=calc._log) calc._log('\nDescriptor\n==========') calc.descriptor.calculate_fingerprints( images=images, parallel=calc._parallel, log=calc._log, calculate_derivatives=calculate_derivatives) calc._log('Calculating potential energy errors...', tic='pot-energy') energy_data = {} for hash, image in images.iteritems(): no_of_atoms = len(image) amp_energy = calc.model.calculate_energy( calc.descriptor.fingerprints[hash]) actual_energy = image.get_potential_energy(apply_constraint=False) act_energy_per_atom = actual_energy / no_of_atoms energy_error = abs(amp_energy - actual_energy) / no_of_atoms energy_data[hash] = [act_energy_per_atom, energy_error] calc._log('...potential energy errors calculated.', toc='pot-energy') # calculating energy per atom rmse energy_square_error = 0. for hash, image in images.iteritems(): energy_square_error += energy_data[hash][1] ** 2. energy_per_atom_rmse = np.sqrt(energy_square_error / len(images)) min_act_energy_per_atom = min([energy_data[hash][0] for hash, image in images.iteritems()]) max_act_energy_per_atom = max([energy_data[hash][0] for hash, image in images.iteritems()]) if plot_forces is False: fig = pyplot.figure(figsize=(5., 5.)) ax = fig.add_subplot(111) else: fig = pyplot.figure(figsize=(5., 10.)) ax = fig.add_subplot(211) calc._log('Plotting energy errors...', tic='energy-plot') for hash, image in images.iteritems(): ax.plot(energy_data[hash][0], energy_data[hash][1], color) # draw horizontal line for rmse ax.plot([min_act_energy_per_atom, max_act_energy_per_atom], [energy_per_atom_rmse, energy_per_atom_rmse], color='black', linestyle='dashed', lw=1,) ax.text(max_act_energy_per_atom, energy_per_atom_rmse, 'energy rmse = %6.5f' % energy_per_atom_rmse, ha='right', va='bottom', color='black') ax.set_xlabel("ab initio energy (eV) per atom") ax.set_ylabel("$|$ab initio energy - Amp energy$|$ / number of atoms") ax.set_title("Energies") calc._log('...energy errors plotted.', toc='energy-plot') if plot_forces is True: ax = fig.add_subplot(212) calc._log('Calculating force errors...', tic='forces') force_data = {} for hash, image in images.iteritems(): amp_forces = \ calc.model.calculate_forces( calc.descriptor.fingerprints[hash], calc.descriptor.fingerprintprimes[hash]) actual_forces = image.get_forces(apply_constraint=False) force_data[hash] = [ actual_forces, abs(np.array(amp_forces) - np.array(actual_forces))] calc._log('...force errors calculated.', toc='forces') # calculating force rmse force_square_error = 0. for hash, image in images.iteritems(): no_of_atoms = len(image) for index in range(no_of_atoms): for k in range(3): force_square_error += \ ((1.0 / 3.0) * force_data[hash][1][index][k] ** 2.) / \ no_of_atoms force_rmse = np.sqrt(force_square_error / len(images)) min_act_force = min([force_data[hash][0][index][k] for hash, image in images.iteritems() for index in range(len(image)) for k in range(3)]) max_act_force = max([force_data[hash][0][index][k] for hash, image in images.iteritems() for index in range(len(image)) for k in range(3)]) calc._log('Plotting force errors...', tic='force-plot') for hash, image in images.iteritems(): for index in range(len(image)): for k in range(3): ax.plot(force_data[hash][0][index][k], force_data[hash][1][index][k], color) # draw horizontal line for rmse ax.plot([min_act_force, max_act_force], [force_rmse, force_rmse], color='black', linestyle='dashed', lw=1,) ax.text(max_act_force, force_rmse, 'force rmse = %5.4f' % force_rmse, ha='right', va='bottom', color='black',) ax.set_xlabel("ab initio force, eV/Ang") ax.set_ylabel("$|$ab initio force - Amp force$|$") ax.set_title("Forces") calc._log('...force errors plotted.', toc='force-plot') fig.savefig(plotfile) if returndata: if plot_forces is False: return fig, energy_data else: return fig, energy_data, force_data def read_trainlog(logfile, verbose=True): """Reads the log file from the training process, returning the relevant parameters. Parameters ---------- logfile : str Name or path to the log file. verbose : bool Write out logfile during analysis. """ data = {} with open(logfile, 'r') as f: lines = f.read().splitlines() def print_(text): if verbose: print(text) # Get number of images. for line in lines: if 'unique images after hashing.' in line: no_images = int(line.split()[0]) break data['no_images'] = no_images # Find where convergence data starts. startline = None for index, line in enumerate(lines): if 'Loss function convergence criteria:' in line: startline = index data['convergence'] = {} d = data['convergence'] break else: return data # Get convergence parameters. ready = [False] * 7 for index, line in enumerate(lines[startline:]): if 'energy_rmse:' in line: ready[0] = True d['energy_rmse'] = float(line.split(':')[-1]) elif 'force_rmse:' in line: ready[1] = True _ = line.split(':')[-1].strip() if _ == 'None': d['force_rmse'] = None trainforces = False else: d['force_rmse'] = float(line.split(':')[-1]) trainforces = True print_('train forces: %s' % trainforces) elif 'force_coefficient:' in line: ready[2] = True _ = line.split(':')[-1].strip() if _ == 'None': d['force_coefficient'] = 0. else: d['force_coefficient'] = float(_) elif 'energy_coefficient:' in line: ready[3] = True d['energy_coefficient'] = float(line.split(':')[-1]) elif 'energy_maxresid:' in line: ready[5] = True _ = line.split(':')[-1].strip() if _ == 'None': d['energy_maxresid'] = None else: d['energy_maxresid'] = float(_) elif 'force_maxresid:' in line: ready[6] = True _ = line.split(':')[-1].strip() if _ == 'None': d['force_maxresid'] = None else: d['force_maxresid'] = float(_) elif 'Step' in line and 'Time' in line: ready[4] = True startline += index + 2 if ready == [True] * 7: break for _ in d.iteritems(): print_('{}: {}'.format(_[0], _[1])) E = d['energy_rmse']**2 * no_images if trainforces: F = d['force_rmse']**2 * no_images else: F = 0. costfxngoal = d['energy_coefficient'] * E + d['force_coefficient'] * F d['costfxngoal'] = costfxngoal # Extract data (emrs and fmrs are max residuals). steps, es, fs, emrs, fmrs, costfxns = [], [], [], [], [], [] costfxnEs, costfxnFs = [], [] index = startline d['converged'] = None while index < len(lines): line = lines[index] if 'Saving checkpoint data.' in line: index += 1 continue elif 'Overwriting file' in line: index += 1 continue elif 'optimization completed successfully.' in line: # old version d['converged'] = True break elif '...optimization successful.' in line: d['converged'] = True break elif 'could not find parameters for the' in line: break elif '...optimization unsuccessful.' in line: d['converged'] = False break print_(line) if trainforces: step, time, costfxn, e, _, emr, _, f, _, fmr, _ = line.split() fs.append(float(f)) fmrs.append(float(fmr)) F = float(f)**2 * no_images costfxnFs.append(d['force_coefficient'] * F / float(costfxn)) else: step, time, costfxn, e, _, emr, _ = line.split() steps.append(int(step)) es.append(float(e)) emrs.append(float(emr)) costfxns.append(costfxn) E = float(e)**2 * no_images costfxnEs.append(d['energy_coefficient'] * E / float(costfxn)) index += 1 d['steps'] = steps d['es'] = es d['fs'] = fs d['emrs'] = emrs d['fmrs'] = fmrs d['costfxns'] = costfxns d['costfxnEs'] = costfxnEs d['costfxnFs'] = costfxnFs return data def plot_convergence(logfile, plotfile='convergence.pdf'): """Makes a plot of the convergence of the cost function and its energy and force components. Parameters ---------- logfile : str Name or path to the log file. plotfile : str Name or path to the plot file. """ data = read_trainlog(logfile) # Find if multiple runs contained in data set. d = data['convergence'] steps = range(len(d['steps'])) breaks = [] for index, step in enumerate(d['steps'][1:]): if step < d['steps'][index]: breaks.append(index) # Make plots. fig = pyplot.figure(figsize=(6., 8.)) # Margins, vertical gap, and top-to-bottom ratio of figure. lm, rm, bm, tm, vg, tb = 0.12, 0.05, 0.08, 0.03, 0.08, 4. bottomaxheight = (1. - bm - tm - vg) / (tb + 1.) ax = fig.add_axes((lm, bm + bottomaxheight + vg, 1. - lm - rm, tb * bottomaxheight)) ax.semilogy(steps, d['es'], 'b', lw=2, label='energy rmse') ax.semilogy(steps, d['emrs'], 'b:', lw=2, label='energy maxresid') if d['force_rmse']: ax.semilogy(steps, d['fs'], 'g', lw=2, label='force rmse') ax.semilogy(steps, d['fmrs'], 'g:', lw=2, label='force maxresid') ax.semilogy(steps, d['costfxns'], color='0.5', lw=2, label='loss function') # Targets. if d['energy_rmse']: ax.semilogy([steps[0], steps[-1]], [d['energy_rmse']] * 2, color='b', linestyle='-', alpha=0.5) if d['energy_maxresid']: ax.semilogy([steps[0], steps[-1]], [d['energy_maxresid']] * 2, color='b', linestyle=':', alpha=0.5) if d['force_rmse']: ax.semilogy([steps[0], steps[-1]], [d['force_rmse']] * 2, color='g', linestyle='-', alpha=0.5) if d['force_maxresid']: ax.semilogy([steps[0], steps[-1]], [d['force_maxresid']] * 2, color='g', linestyle=':', alpha=0.5) ax.set_ylabel('error') ax.legend(loc='best', fontsize=9.) if len(breaks) > 0: ylim = ax.get_ylim() for b in breaks: ax.plot([b] * 2, ylim, '--k') if d['force_rmse']: # Loss function component plot. axf = fig.add_axes((lm, bm, 1. - lm - rm, bottomaxheight)) axf.fill_between(x=np.array(steps), y1=d['costfxnEs'], color='blue') axf.fill_between(x=np.array(steps), y1=d['costfxnEs'], y2=np.array(d['costfxnEs']) + np.array(d['costfxnFs']), color='green') axf.set_ylabel('loss function component') axf.set_xlabel('loss function call') axf.set_ylim(0, 1) else: ax.set_xlabel('loss function call') fig.savefig(plotfile) pyplot.close(fig) amp-0.6/docs/0002755000175000017500000000000013140115452012742 5ustar muammarmuammaramp-0.6/docs/releasenotes.rst0000644000175000017500000000514113137634440016175 0ustar muammarmuammar.. _ReleaseNotes: Release notes ============= 0.6 --- Release date: July 31, 2017 * Python 3 compatibility. Following the release of python3-compatible ASE, we decided to jump on the wagon ourselves. The code should still work fine in python 2.7. (The exception is the tensorflow module, which still only lives inside python 2, unfortunately.) * A community page has been added with resources such as the new mailing list and issue tracker. * The default convergence parameters have been changed to energy-only training; force-training can be added by the user via the loss function. This makes convergence easier for new users. * Convergence plots show maximum residuals as well as root mean-squared error. * Parameters to make the Gaussian feature vectors are now output to the log file. * The helper function :func:`~amp.descriptor.gaussian.make_symmetry_functions` has been added to more easily customize Gaussian fingerprint parameters. Permanently available at https://doi.org/10.5281/zenodo.836788 0.5 --- Release date: February 24, 2017 The code has been significantly restructured since the previous version, in order to increase the modularity; much of the code structure has been changed since v0.4. Specific changes below: * A parallelization scheme allowing for fast message passing with ZeroMQ. * A simpler database format based on files, which optionally can be compressed to save diskspace. * Incorporation of an experimental neural network model based on google's TensorFlow package. Requires TensorFlow version 0.11.0. * Incorporation of an experimental bootstrap module for uncertainty analysis. Permanently available at https://doi.org/10.5281/zenodo.322427 0.4 --- Release date: February 29, 2016 Corresponds to the publication of Khorshidi, A; Peterson*, AA. Amp: a modular approach to machine learning in atomistic simulations. Computer Physics Communications 207:310-324, 2016. http://dx.doi.org/10.1016/j.cpc.2016.05.010 Permanently available at https://doi.org/10.5281/zenodo.46737 0.3 --- Release date: July 13, 2015 First release under the new name "Amp" (Atomistic Machine-Learning Package/Potentials). Permanently available at https://doi.org/10.5281/zenodo.20636 0.2 --- Release date: July 13, 2015 Last version under the name "Neural: Machine-learning for Atomistics". Future versions are named "Amp". Available as the v0.2 tag in https://bitbucket.org/andrewpeterson/neural/commits/tag/v0.2 0.1 --- Release date: November 12, 2014 (Package name: Neural: Machine-Learning for Atomistics) Permanently available at https://doi.org/10.5281/zenodo.12665. First public bitbucket release: September, 2014. amp-0.6/docs/README0000644000175000017500000000016713137634440013635 0ustar muammarmuammarFor instructions on how to build this documentation, please see the "Documentation" section of the file "develop.rst". amp-0.6/docs/moredescriptor.rst0000644000175000017500000000202213137634440016540 0ustar muammarmuammar.. _MoreDescriptor: ================================== More on descriptors ================================== ---------------------------------- Fingerprint ranges ---------------------------------- It is often useful to examine your fingerprints more closely. There is a utility that can help with that, an example of its use is below. This assumes you have open a calculator called "calc.amp" and you want to examine the fingerprint ranges for your training data. .. code-block:: python from ase import io from amp.descriptor.analysis import FingerprintPlot from amp import Amp calc = Amp.load('calc.amp') images = io.read('training.traj', index=':') fpplot = FingerprintPlot(calc) fpplot(images) This will create a plot that looks something like below, here showing the fingerprint ranges for the specified element. .. image:: _static/fpranges.svg :width: 1000 px :align: center You can also overlay a specific image's fingerprint on to the fingerprint plot by using the `overlay` keyword when calling fpplot. amp-0.6/docs/_static/0002755000175000017500000000000013140115452014370 5ustar muammarmuammaramp-0.6/docs/_static/branches.svg0000644000175000017500000004403113137634440016707 0ustar muammarmuammar image/svg+xml 0.4 0.5 0.4.1 master v0.4 v0.5 amp-0.6/docs/_static/zernike.svg0000644000175000017500001325137213137634440016605 0ustar muammarmuammar image/svg+xml0.2 0.4 0.6 0.8 1.0 0.5 1.0 1.5 2.0 2.5 0.5 1.0 1.5 2.0 2.5 3.0 1 2 3 4 0.5 1.0 1.5 2.0 2.5 3.0 0.5 1.0 1.5 2.0 amp-0.6/docs/_static/convergence.svg0000644000175000017500000163602613137634440017434 0ustar muammarmuammar amp-0.6/docs/_static/fpranges.svg0000644000175000017500000102255713137634440016741 0ustar muammarmuammar amp-0.6/docs/_static/completeexample.py0000644000175000017500000000427713137634440020147 0ustar muammarmuammarfrom ase.lattice.surface import fcc110 from ase import Atom, Atoms from ase.constraints import FixAtoms from ase.calculators.emt import EMT from ase.md import VelocityVerlet from ase.md.velocitydistribution import MaxwellBoltzmannDistribution from ase import units from ase import io from amp.utilities import randomize_images from amp import Amp from amp.descriptor import * from amp.regression import * ############################################################################### def test(): # Generate atomic system to create test data. atoms = fcc110('Cu', (2, 2, 2), vacuum=7.) adsorbate = Atoms([Atom('H', atoms[7].position + (0., 0., 2.)), Atom('H', atoms[7].position + (0., 0., 5.))]) atoms.extend(adsorbate) atoms.set_constraint(FixAtoms(indices=[0, 2])) calc = EMT() # cheap calculator atoms.set_calculator(calc) # Run some molecular dynamics to generate data. trajectory = io.Trajectory('data.traj', 'w', atoms=atoms) MaxwellBoltzmannDistribution(atoms, temp=300. * units.kB) dynamics = VelocityVerlet(atoms, dt=1. * units.fs) dynamics.attach(trajectory) for step in range(50): dynamics.run(5) trajectory.close() # Train the calculator. train_images, test_images = randomize_images('data.traj') calc = Amp(descriptor=Behler(), regression=NeuralNetwork()) calc.train(train_images, energy_goal=0.001, force_goal=None) # Plot and test the predictions. import matplotlib matplotlib.use('Agg') from matplotlib import pyplot fig, ax = pyplot.subplots() for image in train_images: actual_energy = image.get_potential_energy() predicted_energy = calc.get_potential_energy(image) ax.plot(actual_energy, predicted_energy, 'b.') for image in test_images: actual_energy = image.get_potential_energy() predicted_energy = calc.get_potential_energy(image) ax.plot(actual_energy, predicted_energy, 'r.') ax.set_xlabel('Actual energy, eV') ax.set_ylabel('Amp energy, eV') fig.savefig('parityplot.png') ############################################################################### if __name__ == '__main__': test()amp-0.6/docs/_static/gaussian.svg0000644000175000017500000577630313137634440016757 0ustar muammarmuammar image/svg+xml0.5 1.0 1.5 2.0 2.5 3.0 0.2 0.4 0.6 0.8 1.0 0.2 0.4 0.6 0.8 1.0 0.2 0.4 0.6 0.8 1.0 amp-0.6/docs/_static/parity_error_sensitivity.svg0000644000175000017500000602452613137634440022332 0ustar muammarmuammar image/svg+xml 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 ab initio energy, eV 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 Amp energy, eV Energies 20 15 10 5 0 5 10 15 20 ab initio force, eV/Ang 20 15 10 5 0 5 10 15 20 Amp force, eV/Ang Forces 1.0 1.2 1.4 1.6 1.8 2.0 2.2 2.4 ab initio energy (eV) per atom 0.0000 0.0005 0.0010 0.0015 0.0020 ab initio energy - Amp energy / number of atoms energy rmse = 0.00051 Energies 20 15 10 5 0 5 10 15 20 ab initio force, eV/Ang 0.000 0.005 0.010 0.015 0.020 0.025 0.030 0.035 0.040 ab initio force - Amp force force rmse = 0.0080 Forces 0.00025 0.00020 0.00015 0.00010 0.00005 parameter no 66 2.186e1 1 2 3 4 loss function 1e8+1.4089e4 amp-0.6/docs/_static/animation.gif0000644000175000017500000063235213137634440017060 0ustar muammarmuammarGIF89a Xõ& 1"""+++444<<<MiCCCLLLSSS]]]bbbkkkttt}}}ƒ¤Íÿ‚‚‚‹‹‹”””œœœ¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿ!ùd!ÿ NETSCAPE2.0, Xþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›´Dx D'þÏž;:¢sh%Ÿj‚ôMSœG<`@„L4à@#R©€@"PÓ§MG¢ö§ ` ˆ&¡*"ÉÓ3J™®5ó×Da¨EÐ@ÂÊ&qPõA„Œö\ÜdÉBHþ+¨[("—„àÀ$ï^ª&S`©i4m‹ìFññW!¬Oáðy´…ÂB¡áÁý\€Üw‡>,±msn°ƒû6œžLvÁʉGÔ¹ÞD ®XP>.$€@Õw_ùeB‹ Áy&d`@U”0ÀCxÀEL(˜Qhþ­åS p…€D|ˆ ›À2V›FF(Î/³hB0Ðq‡ Ÿ`R ±±ÍV[¨µ QøXÉíU]aÖ3ô@›E4|DÏa›ð²sM„šDÑ’q ¬‘9D¤XõMDÔCljµ·šìV¨´L„ãìÕüuÇ“›ð)®? F ÿ!ñ âuß™,›°3«[þæÏí&çý•Î^}& €¶aá–ƒQʺ Œ“λïÀÓ¹î\îžáïq+„{š7'ñØ– f ˜@©ßÄfŸ×¢oq±Ep´x~lÝ ¡ûÌCŒž]‡?©[DÏ*cÏÙ>güQŽmCÝH¦:w!9BxŸT£¬É=‡#—ubà ª¸Œ1EðŒèWÝ=Å3HáÑäÇžA„& !êØ¦¸P0¬Bì6„ñ)D𠆙¯‡@[p-%`&vyfÖ÷½ç…®r:Yuµµ¨…‹³ºÇe¿ at!L¢~Ì»=Up|Ñþ-Ä“)¦±;…y̰>q½)PŒâó$;¡ŠdL¤ r¨CƒØI9zz jöÈ"ôi (€›õA†ö[­Š€¥Ïñ(›´S ó”Q!4,á&oU„b!*ÿ2Â÷¸4T2 f ¤©Ç ~B0á`:™„èÁ0˜&Pf ÛÃÈF„>D'“Àe?Jˆ 6P±$/ Á{Ûzüc,l’ªZü‰ZN7„tæ= k'ôÚ“:[ñŽi<"‚ ¤#˜§™´;Ý®§Ï93Hé›4‘x¼˜Y¢ÎÛÚ¾%åTÓšyªÃ”€þ)a@n¦þ3­AAœBÐÀ  m[“ Ètg‚¹èô9lãPpy‡>ð8Ébt-Óƒ¼à"ð3t01M D‘ +%jn^Üãíj¡ÈÀÐwÕ"”ÀØ€ž#X“ 1$£2²USÁGá5ˆ’k„ËÏ‘­–á°ÉZÒ; I ú‘ àQ7bìš·€Âñ†XËåH®¬‘)€+ÐïžÛà€„u.Ý”šMi˜¨É`ö‹4o+Íiþ—lGÑ”´…0΀àÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^ûú×À¶°‡MìbûØ»þÀ%í„îÍDÀ€œ{Èj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝÞ–Ö4¶S«$€y@€6µdûûßg5Nð‚{àO¸Â{ð…;üá²îY•¡!m˜ú¦n^ý ,e*Aa®†ž©1òi”< §Æcb'Êäf¼å´QCNrmœ<7FÊá±ókäü?wFЙÑsw]ä6O:6ŽÎ¦OCÞ×€º5¤N §þ«Ãêκ °Ž®kýë|ðº9Äö²Ûìä@»Ù×µ‹Ãílû[@óuÀ]îx/ÃÝ¿±÷¼ûý }ïFàÿNxÑ=ƒ/¼â©xm4~ñwÂã—~øÈ[> “¿Fæ/Ïù•Uþ›ï<çC_õÏ‹þôU ý4Tzųå¦o½ì%û¦·©×ö=Þ_ …•»îºÏ;ïu€ TàøÈO¾ò—_ Ô>øÂþÕ@ XÿúØÏ¾ö-PéC?îÃw†Âª¿ýòk¿ûÀÿ>ÛÃߌñ›ÿý×G¿ú Ï~¢Sþð—ÿüý^ÿe¸ÿæ§û·{Þ×u÷€å'€~xþÿ‡€ç×€ Øiý§ ا€hv˜ ˆÖ§vˆ ‚"8‚ZW‚Çp‚˜‚*HX.0ƒ•Æ‚ÆPƒxƒ8¸h:X <€>øƒk„Ä0„øW„FhdH8 J˜3Ø„ õ„Â…ïÇ„TX…S8X€]¸…Õc…Áð… †b0d f¸}Z˜†cˆ†àІ˜~phMkø t˜}ox‡t’‡¾°‡(‡~ˆ€Ø ‚„Xˆ6qˆ¼ˆ!¸ˆŒHޏ È}’8‰2Q‰ºp‰}¨‰7Á‰¹à‰™Š/!Џ@ŠvhŠˆŠ· Š¬ˆ‡¥èx‚Šþ¸Š±г˜ °˜‹qˆ‹4X‹¶ˆ‰ÀX%ðk¹ç‹øàŠ¶Ð‹`à{äVŒÊ8ÌX ÎxÔÇ|ÚÈ|Î'Óˆx»Hyä7ŒÄ( Sä‰Þø Žš'Œ¶ø‰RpŽéXŽìØÕH ×èô˜Žòx¶·Žs(èŽGÐäø9}Éwù‚ùq€Žþ‘ é ù8 ûØ9Œ y‘h‘«÷6(’žG‘ i’ É‹*Ù{$Ùƒ-Ù‘ñØ’+i ) É2Y Y“iG“â÷’D“Yé“o”í'”KH”(é‘J‰”.Ù“‚Ç”RH•M°“‰•R‰‘QéþV™…Ni”_Ù•öÇ•´8ŽP‰–J •%É–fÉ’pùŽj9“syEY‘w—Õp“±“[à–0¹—|9’„9’uɓ昗)y˜…9•à˜˜[¹˜Oi—iteéa †ŽÙ%Œ¹–—Ù~ €©‚9”š`™šÄ'™oI™dÉšªi‚™I›®9˜°©—³É‘›¹Ù˜»™£ù ¥ix•©˜Á’²¹”·‰š¿ šÉ9vµÙ‚›y†©yšM¹œÑY†Ó¹ƒÕé†c©›Ûù“Ú –Í™Ïi™ãé…Ý)„ßY‡é‰œëÃé ʼnØy•ó™”åiïÉ‡á œû9ý©™ç©Ÿ˜÷þ™ê9 ^Y ¶é› zœ“É  Ú›âùŒ *Ÿº õÙ ÷yù)–º¡ÙžIøŸƒx:¡$J‹#ê*¢ñÉ¢-Jy/êž1Ê™3úš5*—* *¡<Ú£ïx£'š£Ö¹£¸I¤Eú£Ð¤ʤ}i¢Pˆ¢·¨¤Î)¥Sj¤UŠ¤à©¢Bº¤Zj˜Nº Ø¦Y:¦ÒС¬ð¡V¢:ª¦kJ¥Wh¥êˆ¥è)§°Ç¥uê¥ð¡Qª§AɧÜé§ ¦*¨gY¦Ê+:¤Šº¨œÀ1<ÁAÜ C \­G¬ÂIì K Mü±)L¾Q¬Äœ»/ü¦W<¿Y¼ SÌÁEœÁÅÆ–0ÆR\Æ(üÄXŒÆ™ ÆZÜŪêÆ` Ç;¼ÅæKÇ»jÇU‹Ç˜ ÇœPÅmûÅ Èi¬ÇÇËÇÈjÈd‹È” ÈbÌÆ0 Äœ’¬ „Ü–ŽÌ¶—ì™Ç”ìÅ~üÈŸ ¡È£\Ç–|Ê…þÊyÌÈôZÊžìÊzˉ,Ë/ÛÉŠlËVìÀ´Ûʳ,̾ ¸,Ä«ÜÇÄ\Ì ÜËõ«ËâËË©yŒ¾–ŒÌ,‘À¼»Ë¿Ò¼˜¿wÍÑœÍÉ»Í LÎÕJÛ˜ÎÈ×àÌÍâ½ælÅ´\·ÚÎIpÌ‘œÌ<Ï| »öŒ—ÐÌÍüœ¹"üÏH€Ï“°É÷Üͨ[Ïý¯}¿ñ\ÈýЛÑ­©ª¾glÄ}É - Ðýº©«Ñž7ÑÒ }Ñ( Ñ*=sͪíÏ?¬Á=Ò‘PÒx ÓÄ‹Ó*ÍÓ¨¬ÏìÓmìÒˆLÔàÓ Ô7Ò3ÍÔŽàÔÿ Õþ¬Ô€LÕ`Õ‹Õ8ŒÔÍÕŒàÕ'©ÕOÖpLÖ‹`Öž Ö9ýÑ;íÌïkÔ» × -ÖÿÌÖŠàÖF…×f,ÔsÍÒ¬Ö_mØg­×öÌטl×Ñ ØI­ØíÌØˆà×ò{ÈQ-ÓCM×ÿ;Ðå,ÙûŒØYLÙ‡`Ù'ÔR½Ù„ÍÅ¢ý×­}Ù¦<Ó4=©žíį}ڲ홫½Ç· Ù• Ú×LÚ†`Ú¾MÊ­ÄÂýÊŽMн}ÜAœÜ„@ÜÍ ÜÌ Ýƒ ÝÔýØÎ­ÃÖ½uËýÙrÙá=ÖœíÁßmÛÙÍÜémËÝØ=ÞÆ½Þ®ÜÞÆ|Þò<Ýð½ØåmÂø-Øâíßä½þÛ‹Üß©ØÑôýï àñß“½ß.LàšmàŽànõ}ßò­áÎ ÞÌnўѲýá}°àþÛÜÞÃ!ÎÉÅÍÊþÉ&vö-â3ãÛ=Ã5ÎÐ ÐpSc€â®â ®ß®$0c‡ç.EFNâqä¾äy0ásxàa úâ/=âÛ=žà7‡ç€S>æa½âÕÝâqP(€>°ægÐæ6Õ9ÉeŽ00‡wp&å7®ã}nÒ1~åÀp³M!@±ØæŠ.æ{^âr±‹1t ké?齚۳ à þÐTg袞։>걎ÇŽz^ê|çÅ\ëwpëךÙT®ÚÀ JÑÅÎæ‡æ³~Õ;¾Â¼^àÑîÍU\TäÉ~éË~ØÛÆÏ®$p“f¢JªÏ ð% h¾^½¹nå¾év°…®22«JÀqSÖdãJê¿þî)¾×ò^wþûŠ÷&ù¦°þîîoï,ŽåÐõžÏ'p&mçÑîû ìn>Õß € Pò­’QLpñF0¢ö/?Àä/ñw@òpó ·*_Bš&[/Ð1샭®ÝMšuA/è-óJ®®çn+-±þ¯ÎìÝþÖÍ>Âߎq\ÿ€q_¿óDFÐfÑvA “±Û±ôLôÿðÅL²&‹²»€m_ªßd Oõ ßñCÿñõ@͸Gßî†ïõˆoø_ß5{³9Û<¹¡ï‘2qKÄŸéöãFóàÈù‹°´kÒ´²c#17µ}ó÷íˆÎêœÎìÜŠ!?ǧ/ô©ïôí¸è,ñíD@Òíñ±˜UÏíº®ÞÅ/œ¸Ýˆ±¿#ð%™“ˆd•ÅMïöòÛµº/@^ç 2âŠíߨÇÞå’¸¿Û/:´QyŽìãõWïÚóÏ¡É_ë,î¢æð?þûLïñ@`‰Ä dR.™Mç•N©Uë›ÕjA€J6"·eóV¯Ùml7é–ÏéÍBÇTÀ(5‚ú³‹Bl°è(Ž®ë‹pÑð/q‘‘ìo’²Òêr°ñ²Óó4ÔN´´²AÂ$"àC ¡3PsSÑkVŒÓV·°Ö8ø3³wìP9YyøwÙù „ä„a$@°ØØñ¶{HwŽ8|ü=ݪ¼û\ý>ž‰T¾>„‰¤„™;ܽ]±löXa²‚¸&tøÐ=ˆèÈ@B”¬~þš¹Y8«aššBN4Yi$¤’'Y¶,#Ñ¥° hà”Æþ~+ͤŒtLŽOBeû©Cƒ²näH6,¯±ßô¦Åû°¤¹Ô6,ÏíaJ!$ {IçÞ:ƒÃÝBŒeÅM1ûú»´³Ä¡ÛÐ@€œ·ÙÕœ¥³·Éh&]6¶8Û·ygÜÝ;‹ÖdбõÆ×ohüݳ9ÃçÀ]沜zvÓµK ½P‘ýòuÍýlßÚŸ»W}{ùhFϯ‚Çùõæ•Çg®Þ9öì›Èº»<Ð\F¿ùûJAè”.Â{$þÿ,ܰ‰ú8T¦A ”l—ó*ü0±CqCY &DøNLÏÄË{"5ÄAyôM«KìoFØ0TiÇKѱH%ôÑIä‚Üo¶ e´‘Ê(ãaRË(¡ì¥ä ÄÒÁ*kó.ÑäñË5g›²Ì!Ç4“H7S<ò';_lSÏíà‘Î9å$±O…ð*ÉBAãSQ5b̰IhMŠL@FÍK{LTSIÿ¼2PBƒš”°H;ÝÎJHO}’ÓU§xIS«È”R+kÕU Ï̵=Fyõt×ZSµRPEnØ<ÕÎ×e_STTƒ5YDeu–Æ:±®ÙmVHa§•Vþ[o'¡ÕÜEqÝVe —ÚqÓ%WÐy7ëÖÞQÀÍRÜrû­7ß®ª¥4`Åðµ·]k‹U5Tx vÔÖÌÖ}¸žƒçM˜àwù×_ŠEŠØ3³8]ŒK]˜Ø†7Y׎Y†ŠdsM¾õÚg>Yå8_nà™ŠÙÛ™%®Y téÖçÿäM:& ÙÝWgŽ{xi¦©0Új÷ˆjKÿE:¯ª³v dÙÆ.Êil…9g¯¥ûlLÊÖm븑IÛÙµÍþÚaª]¶[î›i\kÂõ¦ewù^™pÁæ¾nâÆ}«[í®]<ê£û–| ¬9¯8òe‡œrÇ:q…?ÿöt¶WwþoÑ-gXãÌýžzuÏ_O3ôcG7°íËßÞüsÝw¿³tÙ?¥s·5g<÷Ç?ôä}Ÿ=åÚ¿nΧ^´Þý}ÅY¥/ÿêóÅÏ÷{ðA\?Wòáoÿ(õ­˜~÷1…ßUùïW"/±Ÿþ¨¿üðoUþK]Æš'¼ÝЀ“3ö7<è­‚cË`™ñ¿øQPuÚs ÷ˆç½ry\^öÈ< Ú.z­Û „ÀS)0x.| ‹wB†Ïƒý!_¸½°ý-†bûá’lØ)ŠP‡$¼  eˆ¸%Ö0ˆ "ΠÈÂ"Ž0‰H¼"³xÃ-.‡^Ü¡{XEÒþñ}etâQ—Æ ®ŒmT"‘#GM=±…j”"©¸G>²Æ—ä£xDÜéQŒ‡ì£ ɳî}ÌÓ£Þ%9ªD6j‘xl$}¸ÉRvrKg8/sMPfÓŠ]|e7§øMi²s6ãÔS9wYIž“€÷Äg­ôi'~bÒŸí¨ûÖ9PIÔM}fB³EM{.Ô¡”ˆÝõV8OsRÔH5¥H3šÏfZtþ¢Î¬I“ÉÒ’zò¤Æ\)H•¦RÀ5ô¥Esç-áùF;†Ð•@=Ns*7ˆ®I¢3µé?ij5¢ÕqGESR-éÍ”¢t‚.…*Ĥ &ª2u© m*ÓžºÕÎít˜=Õ$#¹ùѰž­¬fÕWW»ôU±¾5¤cMZ\ͺÑñ©µ—:Õª[±zSʵtÕ’]+*ÓªÖ³q|ݪ_yÅØ¼âµ¦…µ›d¡JÙvT¨Ûôh?1[”€µ©UíjY»Úã\h°ˆMlL­ªTÍÞõ¶?³ÆnyÛ[ßúV±æ‹­l\'Y6³Žkn—ˆ <ºÑ•ît+@´ÚV¹Ä}–q•„\I ÷ªþÙÕ-xßG^í¦»?òî:ÞÚ⦽åÕëyxÝ}6šæ]®x›_Cé—¾F¥-d±ûÞý˜3þ½›‚ü­ô² ¿èdpc<Þù2À î} ár1¨D< gåZ ³÷Á8Z¯p/,Ø›„Ä!ñ‰:`B"´´ìÍpw|ÀŸØ³B­ˆs9JÒ2×(1fëhi,`ÃyÆ¢51=o¼%K™ÈMÖiŠ÷äáŸf™°üMò„qåŒY‹PFc‰Á|d1£Ì>~1qÑlF5בÍQ.r[ßbf*ÇRËQµ1 ÝKàÇVÙ,~s…›\ç9Þ¹•Kžò¢ ³83ñÇþvô‚JÀÒxÂ|‹‘yx9Ïk–±ª vißdºÁ›FР<x‚ |µŽƒ´6÷ÌäB#¾°Î‰«cÍaít ×KXÀv]˜§OÙ6›sŽ˜½ìlŸWÖz€–À|À ¤þ̵µc»»-ÝÆð¶nû àL2à^Såâ9õ°ã¹j<<Ò#–7²è-W{ϧ `°‡&x@ø@×—à>MõÁ%Ýf¶(ÚÍŒ¦ñÃåñ‰W| "°É†C¾V,ëùϺ<ùµñmí²¼=øÖw¯©ÀrŸµæ%›Ï×ð„ ÇHþ²³³ó€0AÝE?z} ¯'àÎJo:¼Û-õ¦ñœ5L‡êÐvЄˆ¶¨MÀjÇ|æwúYÕnº½ƒ®ïE»lÞZkÀ¸Öµ p ,ð:þq}%=¿×WàŸ>ø0q~²TÏN ¦€P/¡ñJˆ€æ2`…æš'1Ôkú/ã<дÎMèW:Á¶yÂsïäÍ¢áÄι‘w.üä¼ød‹;ŠØíb²[ßìîq~Éqý³NÿCÕ_ºì=»ò[sû%-|D‘Ï}Vßüý†¡ýï‘ûõ#µýð'¹þ‰?²ó8þ¼ïÔÀCÄoì²Û4oþKþïöPë‹[$ÿŠmÒRø,ìúzN_êþ¦j—Ï¢îÍÎ.ý(Ð伯½ê)­þpkµoùOùp®vùz÷èÏ÷’-M°û0ëj-°)ìy¬AÐØ¢/kEPå¾+ ÑaþmíOôTó$LøªpÁJP +³P·÷ϰÏpøfð›ÐKžoPs30í¦Ÿàð¸ä°éÐ íÐÒÀ¯0Ðø°»ü0ñÀŽpÌ‘ QËQ½±‹ð²%¼Ð£PÓÈРͰÑ0 O0ÑØðùDŽ7,-ÄY‡ü 1|dþqCA1E± ¡OyQþPÑýH‘)Ât1m. …Q N«µšÑAൎQì,Q‰UÌ;Ñ¥¿å·¾3¯?OÙé»Ìƒ±ÿ QÛ‰¨ ©Ëº¸°kq ‹ïYäýn7¬ Ù°3ñ,©/“ËßM±o ç­û û0{QÕÑ ¡pu¬ÿЙp"±"“q#±Rr!½­#—© Ãï Ur#ò!r%Ë®&50"C¯dý1ƒr'ë‹=ò±°'7e$KñíQ Òñ#ÿ«%é% 0&r&p(þ7O(R'Á’ÎB²—’Q’Å»RÉò‡®RÍÒ$Ñ%OR&Õ’#Ý#}qåÒ³r-sò ³-Ù’SðŠÒ%áéR#õ’&oÒ&·')3, ³Þ¿R3²+g-ÿ’3õQ0÷R1Oí[³*Ó2%Wî4 2¡²)ò)q3*/161í5­q6a²6uó6¥ò(a/µR2Y’/Õ/8±r837¥s7‘Ó.ó²7_í7; 0}25'Ó2+s9/3<ǯ9I2#o1)Y%:y393 3>ó3Ño;%©;•Ò3ç31ë“ïö³<óð<*?Ùþ@ÇS<³<Ôá3@ ï9çÒBßS5ýSCCs4ÑÓ/‡ñC³=­s*Ýó:•óBÉ“A4C‹ª@'ô;1”D½ÒD]s@ÏòEqE½³Eï2F9´Fé“?AóFßRB£'JçƒBÿóGC4IG´Ií3H÷°HõóJÃôFE“1atH'ñK TL{”L±ôLËìD 4MuôH±SDtFôNytK4=ArJå£JKtLÒPËÔEMµWv”Fó´AÙôP7TK;t3µ;ÕG H9•RñtOé+GíCSÛtMû”P{J†ŸÑUSþ+áaT©ÔQõ4KAõVùÔRTUûò"e&ƒµ·ZqVµV#U7UR;µWIsñïãQZŸk¢XµNe4TmõMµ5Wu[33YûQÏS™åXåsW+ôT×ÕJŸÔWK³rʵ¸l°[•RÕuIû3YÒ[ï›è•9Î5;JuRÛu_yu\ãõY=P`kìÉÜTQíô[¹ubÿõb}³K òaQÌ^3¶R–]=aó•ã´Ë:Ö|–: –Yûõ`áÕYçÔÿT½>v:Ídq`yvguUdý4Eóe¹ g«I{hM–_Ý5UvfY6ø>õf#¶dÃU_þ™VaVY¡E•klv»Ž¶8u6gõl“ögÁµbÅ•kÉ•jÇÖjI–n—UbÓNÅ]õVúÈV #SkGÖn¯¶mm´Y!Œo/n\ön‘–b•–m!—á6&W÷ævpëf_öm–fÃ_!öø²_±–I¹ÖTeökå•h-WÐ0ws·lÑÖq1oåŒr±ÒuYÑo•pM·iƒöiQ·h·pÙow°w!Ô6ÿ¶y}÷y™—8W¾rW“÷û–÷O©svÕövK÷xƒ7p…lŸ{!P{‡Öx%Y‰—s…wËw^ãvËÒW~ë’}³x·~C7dÇ× Ï·þa×}e—z¹÷€Í¶v5Vu±Q€¯5SÓõtû7f3÷p™2…è?×XI÷÷Wpc—pó÷dø^Š÷˜¤¶7w„×Öb¿÷ƒÃw1é·`QXœì—u××…Û—‚߀ã7‡¯gƒU˜7XXs ¸…a8•L¸Õ†‡6„‘øˆ{˜†õW†UЉ ˜Š“xc˜„Å„É7ˆÿ*‹G×€¡wzÑÕX»7o«˜[l:ˆø6ŒØ‚ExŠxUÙGŽˆŽIÃŽñøŽ¥ø‚…t‡;¬ŒW‚ÁøŠßu·¸ Y‰y*‘áj‘ù÷‡S÷‘õØsÿ˜áøu͘‹ÝØv»Øg'9r¹„Aþ¹ˆûx¶´8“+Xg¹s£\Ù\ŸXó8Œ‡’ Uÿ*Yƒ.9Š™–‘¹–WŠ…˜³& 9™cyƒ™XážÙ©ŒÙ‘§¹—»Vš—ƒÙXƒYYya¹›5™›Ù—9Ù–ù›ÉJ›×ùœeù›áw—ëÕœÕÙ›•ÙŸ©™œ·œS6 ÑwŸ¿“çÙ‡º û™·¡!]=ø”Myiz”3FòYGßy¯âÙ¡1š—ùÙ+š”K¡U_ÕUc•w€­eÛ®A»¯U-¸Ó˜¯½Ú·¡ ºµZºßØ_¶û ‡{Y `^.¹—`¹›fúòfû~{›À¯»º”ýÚ ä{ºyÚ»ë¿÷þ˜?†¸ëáèz D€èXÛµ•¶›À¹€Â#\Â'œ& ×TË6@Ã7œÃ;ÜÃ7ÜT‹Ô&€ÂKœÂ-|0üÃWœÅC<µFÜÄcÂQ\µ€Åo¼Ã]µ`\ÆcœÆS+Ãq\Èux¼ÇKüÇQ+È…üƉ€ÉqœÈüÈ'<ÉA`É£üÃ<˵<ÇEH¼ÊOüÂSËÆ½|ËW«ËÑ\ç<ÌÇœÌSÈÙÜùœÎ¿üÅãúW àêÊ­#;²'» Hͨ ÝÐÑ]ÑÑÝÑÒ#]Ò'Ò+ÝÒ/Ó3]Ó7ýÒ ÛUÖZ¯§í àú暦7þØTZÕWÕ[ÝÕ_Öc]ÖgÖkÝÖo×s]×w×{Ýרqý6ÀÔŽeñO p öL ª§ºª{§Ú=¦@¦Qš½¦? §«ÜÃ]ÜÇÜËÝÜÏýW`º"ØÛUœ{XW…ôh RaŽÝ&š½PâÝ1è}Ù{ ²E8 €Ž!ì½ðýe˜ZÙe­1 Õ?€¥E`pí˜@æpš¢ºØû¤â/2~  ~ µ,OOlhb5Bà¦= ãàãEÚBÝ­se­Ù=WH`ä™ à8(.:èI~ÙûüT>¾ ¼þMÀˆ^ dîèwfÜ[ÏÛ}½±%é™ ˜[ÚþýR¾õ˜~© @ Ò{ì}æò-µœç¿ã;@Ú]ÅìM@µ™ £ìƒ¾äé~ àæ/¥fÎÀW[õüžeŒ[ {|"@ã4À?ððMÀ¹gZ²)[QòÞ2<ã@mâ e8’`óÝó=ÆFò±åéÉ~Uò~õ— ó5èùÝ Ìí¼EøM n_ rÿeP[ öM¼¿é3ï— ï‘>óŸ  S‚?¹£ñ}F±ýüî—×xôÞ @íu¿ú 54%ø•^ Ì_ÚÒÿeæn ìÎ[H`þàóá英h<"“Ê%³é|B£R# pYr™)·ëý‚¿!²!(Gä0»í~ጀÆópâú=¿íp|hxô"&uph8pp ‰ tt (rv²1:BJŽ dxtH y¶º>1€||Ì…T2fn¾öú"–@ äý##a4€$KûX_Ü.4ˆLƒ'V_[4H°†»Ç-_…l?¿çëïó÷ûÿ (p Á‚"L¨p!ÆBŒ(q"ÅŠ/b̨q#ÇŽ?‚ )r$É’&O¢L©r%Ë–._ÂŒ)s&Íšþ6oâÌ©s'Ïž> *t(Ñ¢FBÜ` YûDØðäB¤V¯bÍʧJƒZ#¢é+‚W“´¢M«vm’x^µÛc–-ݺv‡: ‡ U‘ :03 ÁÈHp”Ü€5á @½J&øÀ q¨.Tóu°‰T!ð€•¼w_ÃŽ S„# B\pJ¤À<45d— [<æÁ%Ç!ˆÜ)€Áƒ!˜‚BšÃ 8x¡Fª‰Ü.xÀ0ׄ[Y·eÃ/?¤‡·¥u›`±‰ÜM F–|£FE0€ÐEeDÀ›†Q@Ý 2Á•~G¸X¢”õQÄ\cNèA0! H„$V’5™ky6A`ÉÜŠDÐ"†BÌ—°€P5¦NS0L1·çð**@ŒFà€bBñDè…äŠåµ^jeЧTµ)lŠ&¶@€:-°*Pµk¢,Úè¤L *‡¡–³¬F ’ÔZ ßþy°™â5û¬¸»ÆéÐ}¸.QBbñQ‚‰µ áÁ~š°Á²Hèd€k÷]fs XBm“ªHá¤î¡µ–ZÜ)À¬X§Rê/¢j¡N`0ÄŠñ'ÄWÀWG̪rbuÖ™fPÕ¯q1o,—v0è'·7£\r  Áœ[‡íZ!,†pÓ@‘!ÖOû)õR„¡ôÖ ÈÑœPÀa­D ÈÁHÀ6¥†j÷›D0ì´Ö¹NýSwì¤ {Ó¬Ó‡— ø’5i–ÞUøÖ½Ÿk®ÀÏ:yLĬm&Žx•¢ž«Cìm¬~§þ“þÝE Z“›0ëˆóý7ÅÇ—ûîfCDï<\ÄTw#Á%À}¸µI`ýÎÖ`ÂðEƒ[—,DäZ<8eÿ´Zn_h‚Ä¥°%ÔXÄ\˜«ùO:[_z‘ú¾–ÍÕÆ{¨€vt2?!Le‰A  J :‘Õ&<ëLöä·¼mO€ôŸûR×@<°xA[’×ÈLÏy€ÞÓ9±˜G=Ó@Ä',.CkÑPîs>\Í~³Ð|„€¾‰%á343‚͘Hó˜€ ” Ø,3Ä à°ƒ|i‹u ¨@ YHd4¬¡ÅèÁÄäPp[,ÎþQØã1A?SBžB¦„€nI¨Í ‡p@"PpR¦*Bà˜% ž/}I Za“Èﵨ-MŒTîˆð&0_DÀb»‡#H !,w*d®Ê¸Dzðƒ¬ß"H%´rŽªB')Çб!*LB ð¨¼"ìësMø 6À€A!OÍ#‚ëúVAÿG'Œ4_êD7„Y).xóñ°)»"dU›Lt¤¾qî•ü™Z¸)Òõ.ŠÂJ>Ó:ßÁn—k¦ÚÂÏÞ½naT ®ØøK†ØQDìdY•³ö%A™§lfÃÎ Z±h £Äô%—Ò”€[³ â`Îþ"™\¹]ðNc"‡që’ÁÔ“ðt”<°"Îà™Eí¥1Eèf¨¤ï„…šH:¸EŠÀI%ª}U‡}fR4¥È†PІ„xÀg àÉ$Ð.ƒÂ`ñéÀë0áKE‹°ªÈ ÀŸÊe€ke ‚@àC•:(à’•§è3$á Xl…FxE±bIs ™Mm0¦¡ „½A_W$ ®1}GÕR–LåOÂ6êŒî)A_ð×¹s¬uíg² ;ë8@3íz*QÍz8`+h€ö@˜DÕ`™bÈ2© ™@»«Ψþ 7CB—¹@`We²ÒgM€èF ä™6ã¶+»âeêË \ §Å9gÓY<—+tùY(—«;ÔÆÑ¿G*Âó Ü$Ê—¨ñ=–œ²Þöà½BYé[%Ùo)K›JaæR¡ÈÀ8À=PÞkð·„*'ÍWÎ|E’É D»!`û«3]6%GÎ °Ê­Ò: ø-ÂLýHØ#òH 4½ìõËm A<©»2 ”!É/G„—¥ÎpˆV•Iê²zЈ†Éwu„‡9‡N´¤Uò?SÈÎú#Â2?‚þN{úÓ µ¨GMêR›úÔ¨NµªWÍêV»úհ޵¬gMëZÛúָε®wÍë^Çú(&4X&>#yfæk —Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÙËøH?2`º÷B€˜³_ÛûÞø5òÍï~÷zßþ¸Àg ðüਆ‘¡¡púœB§Lj wÀH 黳ñæit\‡Ç ÏtNâ ¬îuA9rŽk#äЀ¹3Zš_CæÎÀy3t¾ ›»ÃçÕàù2„® ¢#èì@ú4Ò] ¦SÃéÒPº:¤>éªþ ‚êèÀºÕ·Î­›Ãë\»ÀN²‹ýìp0»8ÔŽö¶«íà€»ÛçN¹{ÃîtÏ»ðÎ ¾ëýïXð»6øÂKðØ@¼áÏÅ[ÃñŒ¼ O ÊKþò–úÆ/Ïù-d>Ÿï|áCÿ Ò‹^ï¦oFêO?÷Õ÷|ó¬=\¯ ÚË>ì¶?:ìoÏ{í¾¹ïý¤ƒo â ÑÆ'Fòÿåå ÃùÌg.ô1ýèÿ²ú¾À¾õ‹§}^tûqú¾.8VoY/üÃÿ}Ò»Mrôs]ü¹àØ*@ÿúÛÿþø¯Ôï~«ÃC0€X€x€Pü×é×þ~뀨€È€’ö·¸Hˆ{ ˜Èè¸uh #H‚‚'Ø|.x+È‚h‚/Ø€ñ0ƒ4H€6xƒ‰–‚µ ƒ;˜€1èƒ „´ „;؃FgH8 JHƒLØ„Nö„²…,8…T(}EXXH‚Z¸…GØ…e8„5H†bXV _Èa¸†(Ô†°ð†‡rh6tø vX‚j˜‡8±‡®Ð‡ˆ‡€È†„Ø‚xˆá—ˆqg†h˜†èˆÈ!ˆ­°ˆhˆ–ˆø šH‰HG˜È ¡X€œ8Š5QŠ«pŠ<ø‰ªØ¬¨ ®Ø°þ‹+1‹©P‹DX‰¸Hºˆ ¼˜Š¿Áx Ãx‹ÅxÇh Éè‹ËhŒÊ¸ χь¥PÖè‰Ðè…’8‰¯ØÛ˜‹Ó8xß޽8Ž7¤ êŒå˜ îøŽ2ÁŽ£0ô(â8ø˜.a¢ÐþÈ 9*Q p‰ ù Ù&ñž‘IÙ y‘"‘‘œ°‘ ¹ ’1’šP’&É(™ *9%Pkç·’‘¸kwŽàHŒTÀ1Üf“4iŽ>‰gˆŽ:9˜H™û”?y -‰ /yxPèh‹LÙ”Õð”—•QÀ1TY•þE‰•N‰‡““–]9•U™ŽbéWÙ \9{j –dÙ–¯÷–}g–hˆ–rù•DY—v™ Zi qù^¹–|˜"˜Y©—C˜˜Np˜t‰—ŠÉ ƒY …™sù—”Y™µÇ˜•ç˜KšD ™œé™5™ƒ¢)…¤9¦™“­‰š¿p™”™Mðšg›²Ù ´9 ¶Ùx› ›¹›ÅЛ’ð›K€›{©›Ä9~Ì9s«™…º©œùœÍ™Öi™Ñ †Óœ¹9œ× Æ È©Ô9šàž³™¯7”Â)çɚ驞¼ÉžŸéžß ŸÞ¹œóIŸ»0žPž¾ç—ïéŸÙŸ¡‰þŸü©ŸšŸš: Šñ)ú AhŸ‚¹pØ º ªy zz‡Š˜¢»˜¢Ç0¡GP¡Ü9¢*Z‡,Z|%ê‡2 £*£3:ˆ5Zœ7Zˆ':™=Zz?ª|Aʈ Š¢û³5·²¨È±ÛZ´Ø‰³«³ê*´%{§L €[ŸP°< ¢þU«‚Wë}HŽ[«]›¡NK}ak•ck²e …_ûŸiË–R´m{…oëœY«¤4Û±uë†wq ™·9´QÛ·4z¶ë™·›£„«µ†ë·ˆ›±ŠË²s[³›‰kµ“›´Œ;µ{¹>¹XK·{׸z º˜+º`»¹b»·K‹º´˜¹ØÉºjëº= »±«ºpK»rû{º¸‹¥@k¹]𻋼Oª»xKºÅkºÇ‹¼Â{´¼+¸Àé¹# ½È(»^;½J{»ØÛŽÚ›¡ÜÛ¹Ìû½¾I¸Ý˵æ{èë¶ã»¶T۾€¿¶Ë¾ô»ïk·ø[¹|»¿Ù¿~ûþ¿¾ë¼”+Àl¿š[¾ž‡Àœ«ÀçËÀ³ëÀZ`¼ ,ÁÂJÁÛkÁY€Á¬ÁIÀ4jÀ¥k½3+Â<¼ܼ(L´*L’$̇êK¾ÄÃËÁâëÁÁ­‹Ã.9Ã>jÂ.ÌÃ@üŠÄ5,¿Ÿ{Ä9̯[Ä7ìÄä)ĘKÄ\Â?LŵiŦ¸ÄùK¶\ì›^ü¤XüÀ/\¸c\Å:œ¾g|Á>\»k|œe»oüÁqÜ»s¬Äm ¿FlZ,Ç{ÌÇPì½R܃¼IìèšÇÔ›Èy°ÈÐÈiì¸<Éu¼¢wÜÕ ¼—¬’Ì”üÇŸ ¡¬È` ÀQ\ʆpÊ þœÊÜÉÏËʉàʵ Ë'LÊ´Üu™,Œ¸|È«¼ËW×ËÙ»ÉWÈz,̃`ˈ0ÊS¬ÌÀ̇à̈ ÍÑLÌÎøËYìÈØ\ÊÒÜÊڌƺlÍc×ÍàkÌ€ÌÍ}LÎdºÎ<ÎR)ËÌÎ~ðÍ…@ÍÁLÏz`Ï„€Ï†¬Ï{ÀÏËÎp,Ï! ÐõlÎî‹ÎU€ÌŒÐg ÐÃÌÐ;©ÎÐî\Â𜖽ÅÉ Ç-Èþ¬¿']ÎMÃm”ÝÒ ýÒC¼Ñ}‰Ó4m)mÊ#ÍÉ:½ÓuÒçÓñÔB­Ÿ6}ÅH]½MÔ¼Ô_lÔýÔPݼþRmÆV= [}ÕpœÕvÜÕ/:Ó^=}ÍTÓÏ\ÖipÖ Ö†IÖlínÝ+-ÆsÍuÍËp­™%ÌyÝÖDÍ¿}=¸ýÐÝЃ=À…íÔkØc°×ýÓÇ,×í‹=œ–}Ù<ÙIÙé|Ø =Æ’½Ï¢­Ø¤ ÖÂ|Ú ½ÙæÙÙž=Ô¬í˰ÍÕ=Û¥[ÛÅ,ÖSºÚºÙ¼Í·M¡²ÜB;ÜÙ˜Ú ÜÈ]®ÒÌ-ÓÎýÜ‘]ÚkPY±|ÁZtÝÅ=ÖÕmÝ«Üs@‰QÚã…ÝÓ}Ô¹MÞ•mÞrÚ³q fCÞ¿íÛòÝÎÀ þ4³qÀUŸÍßd:ÞÿÕÀ!BAÚÓîÞ^§ ¾àLßq .L¤= ßàíß.âÝFpÐ –âñ­Ú$¾à&nLŸQ•at•]á‚záŽÇ.à ÐP§Ÿ<î¨>¾1Ik3IË5^wͶ¥Û“åt0åó{À”^^KyåØýI!)eÎ`B›ä¯:ãkãÞ<æhðiÕVUõ‚älîÐë‹×ìŒåÔ%&L°¥]jªB r±y~ÜÍæìçH° Þ°yBè?dF/ZÞÄ¹ìæŸìèGPàOP©I@eôCýZÍ‹þîéð­êÊ êÆÔ‘N]Š:fb–l‚ªæznîÞÚp~ €Áì•qäF@êH]DpguªëŠNÒŒ®ÀŽ.ìÁŽ ð§ÐjLÐÞôÖ쉾ä«Þë» ëV°íŠôhPZ›~½±ílîU€î¹‚i»Òî)üî¬ÉQqþþðïÙNë„~lB€ëŽJ­Öª¸ˆmÜâíû>ÆÞ ®¿>Ï6ê@®&à§$oàñqýð@ ò{åpòð'òMð®ñ:¯zCJTqAæ! ‡ï0¬ïäå¿;¤´7dè6‚è$oØáïû+ïm€ój ÌÿlÍ Nþ€Y çÅã;/ãGïØ[ÿæAþ#`"˜Ú1hÎåIßõœ-òåÞódàÐón%°ª›ªõù ñ_OÝJO¿Ž^·±yžÙÎÎö£½÷íëè'rE7"êýÞU­ö±Mø¬ìèpp3À;žöyßêwïëaï 0!€@g;øœÿùmúM'% pfÍËúÛÏïn?EÂf÷Q?î®_ø¹ïÄŽÁOá­?üx_ü“ßûbÖ1gx&ü,½Í’ß܆o¾°.kðÝx~üâýüÛ/õÔ_Ñc$5–æêÏçÜÿýßËôâ_‚?ÿTþõëÍ@€LEã™T.™Mç•6K àðL¹J!ÅÉeKeØ +f·MTGÙo»8>×ïù}ÿ0Pp°Ðð1PH.±ÑQÊÃ!à £äÑèëî.ϰnó­³ð. 5Uu•µÕõvo1–@ "„U³Ô”‘´wL M˜Œ¸v™¹Ùù:znVº:©ã²•ïtÔ˜{Ø»ø8ülÜ:]}½Ý‰ú]>q;\Y0˜û^Üüüw^@ äÏ`B5õô¡Ã×ÏÜ>@ùITxcFÍnôx„aE‡üÊÙ9¢I€Y:)fL™3iÎÌÖgÀþŽ93†fÑÅŸ'ÿí”gK!˜6uúªÓ•I©JÛYՠϣDƒ¦l8eɯX«~©pmZµk+PàJî««quãéA¯"ÁÝ;´/]ŒFKå|ØÑ\Äí´~«W,ßC„A^,ò&Ë—9ó ܹZãÊeýÝú²¿ {švŒšuìÒ°e3­™´žÌœrOs=švmu»íl~܉bä¶íª6Þ…x¨Þ 㾤£Bþ6Ì®ÄG´0Êù˜üI³¬Ò/ÿ^S8*ÉôÍȱ<Ù2 q¸3åbs59Y3ÓÎÓ”lM8­ëòÈ<í£ÓMA?ÂÓÐ%÷„¯O(U3Ñ 1Ò‹­t‰+MüÆOôMÓG|l“ÒP ºÔT:ôòQ>9u4UDF­3V¬P­US¿ÌtÒ]½èµÖV –ª[cÍUGaÿû”UbÉÕYžŒMÙ&™ ôUP£}¨Sü¶•¶TS«åòþZHŸÕöÛ0¡M÷ÐpC—T_“˜µP(èu7Ý{åeÅ}…—Ös›Ív`~KëöAƒ=šVÜU±øáAôU85t) ßJ®wI„±TvÌ‹}óxS‘bø]‡Í•Ør]5¹A’u…ÙÒŒ#ÝØfUeNÖåai&qgk>5gCqö7“– Žxh-ƒ&·éPÆôhãìyÙ¨“SZëy¦ÖXå—Y~:^«ÿìÚéuÑvçë›Ãöylµ¹•{í¤É¸î‹´j¬C^z弋˜XpÆöγoÀÅžÛâ¿ãÚqkÚþ÷í¬!œ$ºó&<òt&7ºò¿/_<óÆ ç¼óз3ñÑáfþ¼`ÇQO½_­[ÛôÒc?rÚŸùœïЯVüuÝ™|vßqáÏ&ÞrÜwG¾wå™C:øE£‡ýø°4¯;yë—ažuç=…^t黿zñi!_ÎÛ¹ÇÜûÜ7oß}ñVß|oýÞuÛ£ÞÝ8–¿5a¯yÚKßüHW¿éÝ€û3 uX>P}ôS—ýØÁ N‚¶ë_ÂЇA‚ßá >x&ùOƒ]ñ í·Â]HL/tàúd¨B´ÕІª€Ÿ Gø±ÿ=/€  ƒ(*‚I‡| yøCü5QR-ÌaK–ÁR1†Vœ!]1Ä-^‰^D!¿¸6 ’þ11O¬R{ÈÁ:NÑW„#‡´E.ÎìˆçK¢ —hÇ=ÊJŽR¢£•ôX1<Š‘‰‡üFçøGž•0'` )ÉIŠ‚Tã&Ù¸Æ<ŽÑ“Ye$uÓȃ±²ioLe1(©HK m™„a!ÙÉYzf•¾l%*s)JÞ󗈬¥“éÈ*:3Œ]“e2Q²L"5ó•Â,‡6MjvÅšAÂæ0¹id³œß aÔÆ¹MmR眅|¤:NµžéŒ™>MæMz¶Òž/§9ù ´‚^ÌŸÿ\g,o µbú“Æ”ç3ꛀ²h úq%9ßù³„V´A=QF÷ÙQÆ“þ“ó©yDº!’¾é ‹©Â>ºR{%’™ -[ !úPF®¦6ÝZK1ôRƒš´¤*eçF…zS¢NȨ7Ei)IyÊ™6up8½¦Nñ¦I%î0š"œ*VCL¥ºó¬ùDj?™JVxhUœ\-`ÚÖzÔ´z´­nýÕSU™ÖUªWåWPõª*¾ȯ[+#+ͼÖ°f¥(GïzRÁ²‹°}Üa”XºV6©“ehc5»WɆ•²¢U+h ò’š¼¶ ¸‰ä[Z3ú1”=åW©Ê[–,%*Á.g{Y¡Þ¶’¹ýén V^.Ì.l‘.[ÜBÜϪ¶´Yµn|<ûVÒ¦µþ­ùîûj«Yäʦ@ À%l*$@Bwy5ÞÕb÷d彡}³»YL-@øÀ°%hÁ`2/¹.³vÅoB"<(þf÷¼¬é@À€Ø³ÍªrQÝzµ¹Jѯ…kÓ ƒæ8|?ø†¾¿ºph˱SèÇ}qgp€#„H‚p‹èb¾F`…ÿÊZ¢™Å€Õë9SaÀ@< | ”“Fâ.¢X—Î5e»°,Äo®œ1À—æ17Ah@”Ý H/ÎÅ •SÁh²rù20²¬'0@ƪþ*ô%}ZâN¿9'Ž–TÝ éÅ< 3~ ¦£L€¼:…ž«w[eR×åÖ£®µ:7À_3à ÛMʆÝ[„|Ø ´¦wm·fg5×òµ£}ÜÖ¯ùÐs(ÿõ0»Ïüê.þz¬fÊï“ãD°=4ðç8¶TÐñ$èèOèìoÖêëQ0¿hPÿòô\ðãHp÷ùP¼à¯ÝåpÐétÂ6Ð!¯ KзoΛ«ì 1ã PÉn Ï® ƒ³É 9 ÕðóB µN §Œ p|ÆÐ }жæPîêPõLpY0ß ÃòŒO ï‹aÊgГ° iî -/ ±%, BýOû°Gï%q I1ùM‘öäP÷Ž{0•þosO'‘c‘Çt‘ùR‘yqþZ±LFñ‘ Qu|1ÃüÐïQÿïOQ#:±ŸøBq÷ˆqqñÛø”ññ …±6^Ñ­EÄѲîœQý¸qk±úÀñúØq™±ÍQ6ÐÑ ðõ1 å 3PÛ± KÏ×q»±)ñ GÁOgïî k 5 K]1!óÑ’ݰ!1òü²q"_0$ ²$uì!õg##p!o ¤%9R]’ßï$±Yñ#"©±G’ÂôWqW²+2û`ïêf {²‡Ò(‹’)þU1#Q1&I+›ê#‡*=Q*§±+—ð'É(û«,Ï1'iò%C®-9B)1±#mQk’ÕÑ ­²ër¥â²çò*÷r0Ç2ÃR$ß²oòNs1óºó“'-3+û’0 Ð/ó²(©R ’ Ïr*/²4Yæîr)¿Ò Q US-eó(!3)ó29“,iÓm%16SR#5S'‘Ò¼€S…³5?q8³ð5k2*¿ÐÓñ‘óö>3¥S%%4üÑ;’:“‘;s'ƒQ+×’+qó=,aS/×Ó#™sÓ-·r}S \+¶4&Dl5¡þõx1T@”@kÇ31 ˳÷´S¸**ìAy>·ÑB;”)ZÑ0qR?Ñ>ÕS9I„¦KEÑ¢º¾3(ó0C[$ºVTE[tÅ3C(³7K´2ytGO”.!TòÎ!cÔÁp”3ÈSHÛOBÿR4s“?‰ÈH£ I/CIýÓ.C3Ÿ4>‰t¦”ûºIG5OKaÑGÉTB«óþRL±SLG´8½Ô+åsNåJÝS&ÓÓJ¹”¥Ú>ûóL5´Lµ³MÍ3Mã±PðMÍHMT773ParCïôO¥TO;ÎOé”R5µKÁ3N=µ>Uï.1Hœ2ÕN;õEëTYuU7þ5UótR‡1QCt2á4T59u5H±4B—4 lj?AW]Õ4÷OÅÒWAóP 4-Ù”TËU[V­UU“•DEõGyÕεXøT£ªt1®4R³´I·sV•[ïI\ËêA›•IuBÕ^Ÿõ69•ˆ•\KuL5]·T`§UR‰tùµZ_5[]s[½µGÛ`éUMwPâ6^ÏõWåÕPéÕI‰\§ä]q¤_Ã\!¶`–QL -6;ï•cÕf5Ö"…Õm\vQ 5e£ô1yRMVé–.XV/FVFõZ–]UMcÖc…6.ˆv6V[?Ui™cþÕi¡VkiÖïp¯vamÖj«6W}vWѶ;%Q¹ÖMÃvg—µgåög™6b½-­óXB–DŒvhÿõd·Vg×`£ój·li¦dí6pi¶cÝvcñ¶H!WQá–pQör·pŸ3Z©†oŠj¶lE÷lé6mM·W³ÖEÍ6xfWm5v–qÑ•m¡Uo©åsk.tɶt+un·ng·[‰W×Öõ0]—­Ävi÷nU7r¡·^ñÕpY·y”Wd`uew{i×yWz'¶XÃ{ pm×qes7WY…·’Ê—b´×}Sh3Vz¥YI·u)×VÇó|í·~»öþ~Õ÷vó5V-yáõe×—5·%×€±u_ùw|å’y±6€ç5}6‡·{ã ~iê8ƒ£·„§× ¸zõ÷z)Øo£v„5x€9xfÃ7o×ÔsCØ`äW_1¸vø‡I8ˆW—…ËG‡+†Mxˆ•ø{Ñw†)øcÉÏ…+%½Ô¬½˜€b,` .nç—{ÁØ{?¸i!x…}·tAa«AsŠ#%یÒ  `6À º˜Ù2wƒ¿¸‡ƒ7Œ‚=´CAtw‡*R6¬ÛLàØ” N¡ŽÀ‹ýø‰-¦‘97w#øp§ “Ûwo(Eitºl´>/8HPþí8j ÀŽöÐù€ù§øìŒ;”sy xY“qxy¸F˜‹Y•yä(™Ò, `ÙŠ`ÃøÝ,ù„Q瘶‰x›‘ ›GÉ‹˜¾FߨP¼ ÌÄ, Žàߪ¹†¯Ù˜q—˜µ™Œ™€œó7œc›Ù.™_dÏÀ,þì àãùq.X—%؇hŸ“ÖzÇÖ—ý`¢-z“ÝØb2ZVÙ·ž"¥ÈŽÌ™‘`¢Ù¦ ÞY(À¥_¦cZ¦)` L&<`rZ§wš§{Z§=@&àkfš¨gº¦7à¦}Z©—¨cB¨‹ª_ú¨eþ–Úªyº©aâ©£ª§:&púªÃ:«A`«¹š¨½&À:¬­z¬AàÖúªÇº¬ÍZ¦ÑÔ®}º­ñ:¯±:¨`¨éÚ¨m:&ªº¯õz&øú°sZ®[°©¿z±{z¯'Û¯ú•#à&\–@–iÙ–UÍKÛ´OµS[µW›µ[Ûµ_¶c[¶g›¶kÛ¶o·s[·w;¸PYNˆÍ9 "Y&9 H`l—›¹›Û¹Ÿº£[º§›º«Ûº¯»³[»·›»»Û»¿¼Ã[&>`ä Ž@Û¸­PßêøŽó8>û:盾¡ŠàÞŒ@ßøÍ²xþ½¸¸¾\ÀœÀ ÜÀÁ1侯8QÞù)^ØJ  $`¾à€à¤DÂ)ÜÂõû)fYJ"á߀~0\Ð6œbâX½ ÅÄL¹? ƒD`ìŽ Ð¸˜ìؼ‰äÆsv\¿`Á`âÜ‚¤ >€`‹ ¶Ø~|‚œ_€ÛØ ÆGž1…Šü@²a Èd̼$ÁN´¼püÍϼm͆•À•!|ZÌɼJ€A[Ä«¤ÍÎáx– =¡íÐùe¤›ÙÒ%Æ×+@¾å$Ñï<ÉŽ 8L:ýÍ@þ` @ËGšM@>ÝBý˃e ûLù&ÎL ¨ùLHн–ùÛIHÝ2< ÌØkÖY¤Ü‚½†]a `ÒÙ #¥`Òâ\У=Ì€ÚýÛ“À•³]J$Ö¥ÝjyÕ¿EÒHºÒ1…ÀšýEHýÕ•,ÖEÜÝ\ ²€LÔÝ ‘Lß‹@Öõ<Õø<³Ce³íEHÝÑ ÍË}LÔÝßM ÐƒÛÞT¸Ü<ÌTH`"ý×ËÝÌÑ\Í'>ã[Ö¤–?¼ÌíÜÕ@æ ÓÒ;ÜÅ$€>`×{ÝN:€Ì8€.!Р:È«„èþ¬¾FÀÉ= $€Ù¥¤–Oü –{¼éŸžbê½ÚN0@ÊÌÏ­¡ ‚ßR<ÃYÜIÞÞ)`H Ô <¾>®*2AÅ5<Áñ_ñŸñßñò#_ò'Ÿò+ßò/ó3_ó7Ÿó;ßó?ôCß6àß ^Ý·“à:@ô_?OH €ÁF@Ø^âÝÿý`Ÿ÷ÉÐ߀ÿ€ð¾÷•bÔÝLÜéÙ+“?NŒ¿9`ú3$!0œ’À>@Ê@æ_.  ^½ÒÕ=ž}À¼ŸâÚ‘ßþ'D"þ@ .õ‹ ÃÆÓxL¦PÀÀé0€‘©„<:D ¤t Or!d<D) hØ(/ÃŃ <(0€€x)%*.26:>BFJNRVZ^bfjnrvz~‚†ŠŽ’–šž¢¦ª®Fzp(I$PU ´™4$L™ØE$– 8|Ý)"t$>ˆ˜¼(¾á™Èb$B$²†‹“—›Ÿ£§«¯³·»“»Âšè% ,3( 4(ÊRi¡¤Ä.É&â¢èƒ²7Ä]‹@š€¾L€{§q#ÇŽ?‚ )r$IJÈ“U/_ÂJ°(ÊL€šþ6g™³hA5ˆ2L°Í§‰M¼•±$Ó¦NŸB*u*UMñòà41€¥÷´r Cfš€¶Ha"Wxì u£LìѤJ–VÍ«w/ß¾~ÿnûJ"­°\îê5Ѐ­ qé\ÖìY°7ßÍàè†`&ö-z4éÒ¦O_:)ž’†¿2q¥€ç+æpˆpA‰ZEfШaã&Àc;Ø$(®èÁ€‡PC.}:õêë®j^¹ ±‰\D¸‚ÐD‡ h$­·e>@/Áçå¹zêzÁwëþÿ €v“&˜ ‚ 2Ø ƒ@PGd†ˆ ==˜¡†rØa^pÞØd‚ ‡'¢˜¢Š+²Ø¢‹/£Œ3ÒX£7☣Ž;òØ£?¤CY¤‘G"™¤’K2Ù¤“OB¥”SRY¥•Wb™¥–[rÙ¥—_‚¦˜c’Y¦™g¢™¦šk²Ù¦›o§œsÒY§wâ™§ž{òÙ§Ÿ2!ùd, X… 1"""+++444<<<MiCCCLLLTTT]]]bbbkkkttt}}}ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›´Dx D'þÏž;:âÓRÑ4 9ê†)΋ `¨ ° ÃŠU è(Ó£#‚FqŠ„ƒˆ0¢IØ#ˆH¶̀KÕšaÛ÷éDäVuÀ0d€€ ˜/HÀ¨„ˆº&˜z&ô'Ï Ó4À B„p`r×HÞÔF ùØt™´¾C 6\ÑÃÞ‹™dØY‚Hs!/Ž„º „ Âê‡%µ‹€Å]Ä:ޥјOO<£‡äJJŽdÙ0 ²n}@šH[APz&¼GöõÆžpVù€€Dø$‚~0õÞâþÑ—–jµÀv$<0@Pˆ„NlWÄr€ÀãñµS D8aL€6 }lå§Õ¤¦ˆú0’Hvb…µp„ˆâ‘íE„œUJt@%t€Aß!8€` ¢ ôç_Hh›4àXj8A ÈqHDt ‚©N °@dT])ÜTDðL!w`eL€€šX‰Bà@lä šEäuç±á@¬C¤u*˜ð¥=b ™V¥¸á…›(ë- ‚Ú²¨FàUÐЍ¢Ò}P¨þ˜I°·Í~ À­e6ô­H`'bbåYY—Ùʼn„èiÂÉ¡“©½×£ÙõWlômX«‡jUqX BbÏéÀ¨’ !±œ½†Å™ÄÁ°ò À篞ŒDXši&fTñ ÔšÇÕÜ[éÉ6{ì¨ j2À°#]¤¡ý˜0 ÀÒï9mrP!5“סôï-/Dó2Q(€PB1«-Ä0w¡-¦‰@|Dcd !hsKrô5ÚᤂY§“Bš¸@ Py¨[?}&¤@åØmbðÑØË7óÛe> G9þ¯~y9!Lýy­‘ø° ßÇT¡…›asin‚e¾+>ã%§“ò̯íЙnÃ:Ä€ø[Dc^'Á%Œ_y°I ¾ÎRƒ`ÂìEÄVhwŽ`©?Yœ?¦ ágÆ¡@ø42R&kB(P«uHhÌOr¦>ЄçQ»J²Šà§•ÁF~ûêÖŠ 5! „&(Að'¶5©o}í…PÂ÷ep{ô[Pfh‚©XÏ!m[BcH„ÄÍ,7‰a@¾$”_®ëž Àr¾ùÔoiB ÖÅLcøˆìbôÒý („â¹Ob†9š!MüŠÓþ÷…B³ïá I¨š f&@)f°ÄÆußaK 3Hªù±†?\H•à§š 16+‚À„€çCŸ “ðAçèîk+ KÖ Át±ZȨ&*U²h¼_½îh›w)aŽYŒŸ`ÙÁ÷ A£œ¥iCªPx‚M(£ÇG!”—·td9IIz±:ŽY#4U590 Ø@™â%áw‹;eî|£hî‹j]å^ÕF¶˜'w´š5q KH,6.òÜdD£!-³Bí,'³ r==œE‡ÊlºsEÃù£Fáù¼‹~S‘ífB&þ¹”„ áhGTÓìBP¹ &“FX2`É ýÈ¥%ˆÑ à0ˆáP )ÁÑn©¿ŸÐ ˆÐs0*­8l©ÚÌ%€¸´;RüeJ‘YG‹ÊªRí¨Ã•´ÖlE=%SBðSçÄ ¥OsXÌè£@X²…™*-à€¥ö©·!Âò²UÉæn¬9TŽ8MF7<­¬ŸX0@.Cˆ `rᆂ@ ÅŸ˜ç•Ýr—¦3Ô>æ«]”2M`ªZ©Ñ k`óX†¡F ]€„$ [Õ‰ YB«D{,EÝg=-ã¬gA»[À&ÎX¥%PF°þ³íH7°ZðÀ[¶Ò÷!ÐÊŽ¦Z×ѹC N$€ö>¯ŠË8z„Ë¡NIøkÜê?Y¯ 0—°É !k*˜p$ß[Ñ68ÞŠ–”Õ‚*ÏnËèb—±î+vú¬„à5¾is!¬ ¬$±%&ŠKƒUô×±b I Š*ôY/u×ÅèµB§% u)4À6à£y¯$ÖCe1f.”y SŠò&X a9î5AiSæŒèm{G#§šá°€s¡ÏDHYì>’”DJžÊKO÷Ü%ó0‹3T–C‚aŽtùD(Â)£Ýð­þ8ãu¨Ôó¦G ìQ•¾AލIÍj•`GÓ•‰´/‰@‚ |¸Îµ®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØfö6 0i ‹FôÍ£(šûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸º Ô‚l{C€s €# gÙŽ¸Ä'ÞkPüâǶÅ3ÎñŽ;{ã¹È‡Ý²/?#e ¸õní;Ø-|ËÈ‘É7æéj,z7—FΡAsjÄÌuÍK |;|„žccçÐ@ú3”Þ £¿ÃéÖ`z3¤Î ªþ+êíÀ:5¬® ®'ÃëÇÐú:Ä. ‚SÃìÓ@{4Èž¶·úípû9ä÷ºïîåÀ»Ý÷^½Ãï|ü þði0ü7øÆñÝ€¼ã'ßÉoÃò”Ïü0Ÿ ÎkþóQðü5DúÒ/ôÕ@½éW_ÕOÃõ¬g=ì×€šÇþö›¯}3”|Á3ÿêÆo¾ô™ðüdTúp¿~Ø£ýî·žûY¿÷½¯}c”üŒ>?1Ôþ(³_ïo¿Jã úËÿ‡ö÷Eþï¯ýóÂÿüG¨ €8Q€¸€€X hþ ¸€2ñ€´ ø( X,‘°À˜è !ø&1‚¬`‚$8(¨ +˜‚ Ñ‚¨ƒ.Ø2h 58ƒqƒ¤ ƒ8x<( ?؃„ °1×lg&„kC„Ÿ°1÷f{JØMLè 3p…X˜…Z¸…@â…‘4…°1`fx†h˜†jhð…`h=bÈ d¸†t¸†m…o¸„nØvP†uø‡fx‡yXq¸ sˆ(ˆƒ(…{ˆ‡ˆˆt¨ˆ‹ˆ8w}‰u(‰“¨‡xȇ~ˆ‰j¨‰›X&…¨ Šh(Š£(€•h§ˆŠØŠ«8¥˜ ¯þ‹ª8‹(‹äp‹¨˜‹ºhµˆ ¾ŠÀŒ41Œ—PŒ˜xŒÈ¼øw—‹©ϸÊh ̉Îxh…7Ô‹èßXŽ–ø‰ãØæ¸àÛˆˆìØŽ*‘•€8ôˆöH ø˜ˆï¸"Ñ“ð™ù‚y⸎ ™4ø—×Ô¨©‚© ‰y‘A’°‘vØ‘i  "Š$Y’C¸’Ö’ih‘,é' 0Yè8“!Q“p“g(“:¹<é>IŽAY.™z‰‹Iy” 1”P”lØ”N©PÉR ”Ui’T) YÙ•þ[iW¹_™“a™c©ey–$‘–‰°–l9`ÉsKù‹s—á–ˆ—xùzy|Ù—i–½X—Æx—‚Éi™˜9ˆ˜Å§Ž ™Ž‰‹YY™>H™Ë™šY—Ižù™šƒ0š¤¦)¨™š±šqg˜ÍÈ™® ° ­Y›OI›Ö'›ÜÈ›ºÉ·ù¹œ 1œ~PœÆyÈÙʹœÑœ|ðœÐ9Òyw¾)ÀY®¸æ—ùèÜ)„)’É”å9žÿpz@êéì™î)%mIøžð'žëž™žRà„öæŸøùú9 óz}Èþ… Ê…^( J€j %•’JHÐ2üÒm Sãø“ú †¢¤ ¨’8àF~¢ pk¶·¡OСj”"Ú›Z ”¢Cà'qJ0ê2:£ZY£·Ÿ_@cd-SŽ?ÚAú¡CJ¤µ`¤^€¤§¢Ž‘ àRBФÔ*¤$*¥÷8¦¥€¤:*2Ð'Â¥^zz`:£Sy£dŠfJ hê"<§Jð¤i§wÚy: {êŸõ}çi—„ú¥a ¥‡Z¨D9©¡¨M°¨D°1žšÚŸb ¨“ù¨”z ëĪC× dWº"~ʨtþ¥@:§bjª§Ê‚–ê À~ÒÀˆsC €àð¦²z«£j«’Š«¹ƒ»Ú †$Âô* q,2 ‚büyЊ¤ŠžÑZÓZ¤ßÊ‘áZtÎ:¨åú Tj€šãê¨ïªçê€é:’ëê­‘ê®÷J ýJ ó*®íZª+°»·¯*9°›z°äš°ê°^ɰ19—õz˜+±¦˜¯Sj±8Ù¬ÿа›€; ¢ Uk¯%‹®+ |)K£`±³³/ë'; Ë®#±9‹²8™³Š±-«±Aû±C»|3[§"[´K›´•µÐª7û´Ì*µxJµþ½iµ¿¹²6ûµZ»µ ëµÚ ¶G{µck¨\‹ =ë¯P»¶l[¶q‹i+¶r+‚;‹§fžhû³.›·'¸·±ð¶ß¸H+¸ƒÛ¶Û×·¢j·ˆ«¶Š««ŒûŽ ®Xû¬“» ñÚwMK«Nz·g»¹”K·Y[³¢ë·¤› K†û°‘‹·«kƒ„k¨—«®™ °³[ ­;¯«1©û¸»»ƒµû ¿Û¥Á‹¹ÃK¼•[ Ç˲±;ºË; ½+϶Ò;½¡P½qp½É‹»Ú»½Å«··Ë¯¹K²áÛ„ãÛ Þ½ª›¾êÛ¼ûY¾ {¾@ ¿r¸¾ƒK¿û·u‹¿ù+¿ú¹Fëþ¾Â À+ÀùÉ¿! ¹ÿ‹À¶¨¿œKÀþ{ºLŒ¬« ¬²ö¸¼Œ̺ÜÁ‰ûÁ ¬ÀÁоlÂ:‹ÂæºÁ4ûØû¾,\¦. ¯#ìÀ\Ã!ÂÒ ÃN«ÃšËÃ-lºCŒº¬¼DÜÃ7Œ¯@ ººÂK<µF¬»2ü½æ;Å(éç Â;¬ÅXÉÅ´ûÄ,Å`¬–bÌ»9ŒÄf|Æ{™ÆÄKÆ|ÄnL–pL½k|ÅI ¾ulÇMÜ ^LÇ}üÆL rLÂ’;È„\Åè«Çm¬È¬yÇ@˜Ç^0à ɘ)Éâ{ÈBlŘ,šš\„”\yX\¿ŸÌ˜¡¬¾œÌÆ_|ʸþ™ÊT8Ê\`ÉJìÊ‘\ȻȞlËÄ Ëc(Ë[@Ë|Ì˯ŒËºÊŽÜÊČƜ ºÜÈË윾œ¿È\ɥܿÑ\ÌŒ|¿Ö¼ÇYœÍÒÜÌ&[ͤìͦ ÎÓ9Í#Jγ|Í ŒÎ̼ÍÜÍ Ï¾«ÎËÎÁìÎlÏí‰Ï¬ÏZ ÌßìÏwÀ½pðÌÜlЮ Ð,ÐY@ÐçÌО+Îè ÑX ÑØLÑòêÐ ŒÑ¹gÎÍÑ mÑú ÒV ÑïLÒÖëÑÚÌÍÏ1ÌÒ íÒeŠÒU ÒýLÓÝkÓ:‹ÓT Ó3ÍÓm€ÐƒÓ-ÓALÔGmÒ ÔɧÔPÌÔIíÔ( Õþÿ)Õ>¿Fí =ÏT­]]ÔHÑZmÕà<ÖlðÕ%Ök Öo]Ö!]Ïnýx[ÝÃX "½ÒuÝw½ÅyÍ¡gÝ×qÖ|K×s­Ì„ݬ†]¸rÒƒ½Ø~ÝØ¶‹Ø½×;-Ùe×býØ9Ùš½Ùm“žÔ ÚvMÙÆ[ÚQÙCÚW¬ÚäkÙŸíÚK Ûv+ÛìËÚYmÛSÛrªÛ|PÐ °PVÍØ1zÚÀmÍ­$€ {jdÀÖ‰üܤÝy`"î£{àgÙÍÛzMÛÜÕÞÐFºçÜdÚ-»éÝÎë}W–Bº×ñ-ÚÌ]«¾=þÚÚ[½èGîsäm×ÿº~ߊ\½ @!kv»àQ¬ØõýÙ^%3Õ]Ý`Ù.§ ÞáÝ- à Ðj—áèÝÚ3®â_úáj|âêÜ6Ù8Ç5~ÞÞã‚ýãrðIî×:N¯«ån¼_nðýVš —e ·ç~à‹þçj]FèIºF~FnÀËçp;ä¾ãî €Ÿ¾PÝ1^…ŽýE’þ¦èš~ÙîæÕ ꟎ &§˜jjÇê‚\ίÞåœ ¹~F¼>N¾¾ËíÜæ›Ž ^ÅŽ ²v+ó½Í^ä½°rÚþ íÝ.éf.n(ãP,Û©ŸJ¿¿íäÌ~íIª«*àZ n„¯úfg ·¬¿¾ìÁåëÏíÜðßÞI¾Š5Á:¬I¥e\!_Ví4Ìîµúì‚P­a!L‰#tñ…"©ñ—,ñÁ-ÏmMÏ­òÈKñl@ÐòY^ò¦½î&ïä(¯# Yš'¶Üý®î?ßãÕëäS{Ä=\óû¾Ï4_óìzóH1Æ'þnæó2OãW/ñÕß1kƒ~_ËNo°P³Q'£½öæ½åAoãCO€©Îö˜~¸o¯âÕ ³¡Ta±ƒfÍy»{ßáÕ TÝæß‡oøYÏîÕ»@°øxßøÀÛôcÿ°e@\P<òÝöÍù™ò‡æ Ü2þønïúÍÞÕm2YLnùÈKú¥?Ö"Õ]û°Ïè¿ïèðîÄ-5×2i¨[ø—oûÏ]½êE÷IÊÊûÌÜ‘/ô•ü¾ô¥Ï²›o~ÔÕ½×ÛÃîûàŸÔ¸ŸùëßýíoÖï?öñúç/Ô·þš/þòÛ ì@`‰Ec2-™Mç•N©Uë›Õn¹]ïÉeó^‚’j÷_(FûÝ‚TªÙüHOn°Ðð1Qq‘±1ŠmÏQrò‹­ðOÀO“2Tt”´Ôô 2•uÒÒó³m³öH¶7Ww—·×tÕ7xó²¶­¯Ø6R¸Ùù:ºXº:ëU™èø,9;ðÖ:\|œ¼|ŒÚÜÛ;œ›“]hÛ¬Äþ?_?¿$Ýÿ`À-èV[çm^™nì’abD‰)JdVcFánœF'^»‹f"tG’N•+Y¶tYÂI3iÖ<ÕÑæ®þƒÙžƒ¯§˜’<¿I4B,adÓ7E{lÒRhȬÐQy}‚Ï#?ÕPÒ@3mÐ5OufS;‰Òà¶ÃtU*B¥²VaZ½sÃ6¯œ1V÷r­/Õ>!Ö‘] |ITKUµÃbE=öÉ[ϤÙE”ÙQ›°6Ol÷”þ×lA%÷ZsyÙ¶Ñ^+öYcƒP]R­·6oÏëVÜ?ãvÞ%ñeÜGÆ…]úø 8ËY­Ä·`Oû=8Ž„ç[Øa_ßù·Ü#†•â¦ô½Ð]Yá½÷d þ¸Ù«›Xá’…M™Ö_UöÝp]FÅbù0¦ùa”8N—b–GÞ¹<˜/–™^ 5eœ‡ÖéRzÞ—i~׿š!ÎÙàªI¹šäì¸Þ8j´oþzê°Å…ìñ~î:h…À–8d£•~Û§½±6ÛdºŸ¶»m¼‹¾d¾)‰[¼¹ÕöêµëÕ[ñP2놷üñºÍ¥¼rWŽn<óI3>;rÈÕ=ôdGÇþð™Gð´'G¼åÖQò»lÛk—üwÕ?ÇýuÝQå]îÒMÝ\öξðĬøéOÝóª|‡~ûé ¹œºëƒÏ­ès¯–xä½P}Ø»/ÿý¨ÒÏ{þõßzå¡sìŸÏ–uö3þ ¤?y®ãû²(ÀïU¯€±kÿšç?Úqx4 #¾ò˜ïh ¼Û§Ás•0Ô'è¼ °~&T Od@€!°‚ |!c(ÊÑp8Ô^‡(<’Ї3Daãç$â°…#lbûV+!ˆX4"ŸHÅ7°ƒ6ì˜ øÁ)®J„^|ÇèBͱþp…dÌ!·¨F´°±†n4ó¢¨Ã2ñŒvÌ ƒ(F¢ñ1Žü#)HçR8W”_áøF¶5Ò‘èäm$éDLºˆ’—Ìb&]³IÚtr1Ÿ¼ù†•FRzÇ”³A%{T™¤ZºÉ•¯,¥ I5DZR‘sä(ué¢XÊf–¼¹e¤–©¥\ó‘¼Ôãò*¹ÇjRS”u„¦ŽYšd†(”Áì#Y¹ÍãI³™¶ê¡Ç9Lmš3M@Œd/ÝvÍý±3‘«['<£‰Fzî—ÖÄ'0õN~jÄL¥BiÉÐS=ó êì¦h¾éƒšÑ¡™‚hDÇ5ÑýSz¨8óIÎr´þ£ }gCWªÌŒZj£(=¡J˹Ж‚3NŠ©L¿%ON‚ô|ĺhkêÏ¡ò `ÌJ 0€€QØEH ¡iÞ“¤ÅèMaºO¤¦”> @>ðl €@ð{œëª (6³:Ò‚æt›JMJÎÊ ­·SOãzùbÕÂdäKeªW¤<@M˜êž0ÕØT”xõ—fm´Ó¯:ö(8@B€ q8êZ÷­ýu9„ý•Z÷ ×Ç5öºcnn;Þ5(÷¾Ð}ÛÍ6º¿u÷/ê=îegùÙƒzwÂ+—ï‹CüáíM¿ÇðI¼w+Ÿôȳ”êúÖ¡nk>‹8ôšÚ|¼ÕNm`~î¥_ûé×Loެ>á;w}µij{a ¾ëŸtç÷.|G^Þ-™ÿ·ñMüÂã^ä³Þ=ïýÉø4;ò̇¶ö]>òéo¹÷l½ïÏÍýtÞÊ©W¹âŸný<ãÞðÃGÿïÕosØÿ]öS'fÍ_'ì'ºÖvùÚBùì…ùâ¯0þ÷zNÙôܧMÿÄïöþõ­ïóò/Ì€Ïñ<ì ¯û¤ïû- 0û@Pó,°8Dç^®0ö ðpi¯ÞX0úBîûÀ¯ú¼ŽpûT°øˆ0ñ40ù8õöoÏ«Ï\0í0ÐõLð+Pó€pþAG{ ßB ké UO¹í¦ï ·Â ç ¡0àÎð½/‘ÃpüưüŒ0_ä0 mÚÖP+Ú°ù°°ÿâ0 ù3I³‚p ÏïgÏ ËLñM¯ÛÎ9qÁñ70w0 )1q QQêDÑOÓ¯ ³þmpo°+í«0ÉP±þ>âY±õ\1üpP± SÑq[Ñw K /ð‚QWn±±‘중 {q¹ñÅ1‡™Q1“}± B—P#n“çA›qÏqÓñþâQòÌqgÑþÔÙqݱ!áÑ åñMÌ!û­[0"Åp"EO!ÿ‘!Ò!I"‘Ùȱ˜‘)q ;Rt`r$E²Î*Ò/ò‹Q"²êd²$iÙBR'=’'sR ŸÐ'Ã1#á‰%“Â%ß0)×-*£Q(r'M#çñþ"ñ%Ðr#!‘ c%iQ%u©)‘â) q#‡0-7h*ug-¢-r.î,Ëm)»ñ*«’(µ’*ÁRó’/¹2!Å2.Sp1¯°óÎ05²1¯ïð¿2æôq(sp/%³/iÃÍàìÍäŒg*/S3±20²';sðÞ’åM6¥±.â.Dz,å"7r3“‡P íÑ$Ím²%q2%?ó‘Óó3+Y3"S¢þ’0W³7yS59“9©ó:“:‰…8Ò8ÑR9—o#CS4½„4ñ± ýÏ5mÂ+“F:3“·Ó:Ó5dÐÞ¯;Çq<m?ÿä;Ù2<ó#þ1ãé7³P„S鳓ÿSóÔ.Ô3ô5'³ñ²=ùCJBËK¼Ps>ís&«S)?ÔC T/! CTC(Ô6-4Cû“1mÔ1qFoTEei7ûiDÝRCÉ*;49{ôÎv”ú‚”C›TG…Gkp:IFI}IŸÔI‡ô%t¹Ô#Þ³Š3J7KËTK«”L5tâд6sâ6ÏJ‘”<Õ´M#ÀôŽâS÷RÓDësJO”>S”E7M=íGIOÐQt+õBµFåT.½t#ðT~dÔMi4øÌtL‰”RyRAôS—T£4uE‹Ò@CõT3UQµEaþóéØÔN§1NUõHmµK³tLMÑ“tT­´TItQµQ]•@õQ‘ué\ôeSÝÓToUY7NuUR)ïW ÂRa)Q7±DÿÔOåsXûTP§õê˜Õ"µ[ô[Åõ ¯5G«V1sOÛE]Å^ݰVÍUZYU<óµAóÑ^³µM¡UXßWç´S·]‹Õ_kè^¯T^'va‹´aÓ]}æP _Ö9`=õb[õa )bƒu_K6R=Tù5Wç•OÁsN¶«¶9cëu\eÖaóÞFV@iVQÞ´bSÖgÿ•e#0c±Fh+Z_–bC–aa¶]uviš–ÄþžVa¥ÖbA6i¥ti©”`gõmV;ɵgq–döXUè²ÕP±ËÌ6¿6M‹ÖZí–WÉ6ãäVLˆ–kVm‘Öe‹NculŸµ&×nEÖk wR¶BýV:˜Ê© J H²<@óèPÑ6ga!·eÝö\©v)÷8R¬¬úê JÀ`<€ÀsmìqQ·_Ù6YyjwµN¥Ñ<ÏsÒ“=%7ž7,Œ 2 2@d¤lw}wee‡Åb6lC—gµ`{«–t-?óS²$Qu/u>ø« 8(Ë ÀšLw£¶q{ˆº¸×jo–Xµ`Å×þ[Á"%tAÁ!€ ˜u‡ãª7½Lë X« ø*w½ tõÏv¸twkõ–ƒýWt¶fB¸ w>t‹·|ë šà Èæà‚ËÕƒ?ÖJL¸nM7^wU‡½·{]‡’~XˆQX>¦k…¯‹ JÀ…aX†ïo¯÷ˆØ†±—Z}X=OQ£†Š¥ÒˆÏC´Hk½ž`:l (Ø bX(€ÛØßŽ)`Î <`îóX÷=¦jâXãxŽ7 Žù‘ÙïÙ‘Û¸ñ™’õx‘í¡‘Ù‘#ù츒?ù’A “5Y9Ù<ù“)9”AþàR¹’Cy”IŽMPÙ•ùx•mù–-ù e™éø&y—q9t¹˜ï–}˜ƒÙ;9™÷8—£™—ù}çú‹ Ü~›€æ— ê7ɘ`ªÊלÏÓY×™ÛÙßžãYžç™žëÙžïŸóYŸ÷™Ãw<œÃ4Ì ¤~©÷ H`ÜÊxš¡Ú¡¢#Z¢'š¢+Ú¢/£3Z£7š£;Ú£?¤ú6 ªæ#Å<À¬XŒ¿’Lv  vo·›{P¦gÚ ‚lÈ ÈŽlœIÀ†¬si¨ƒZ¨‡š¨‹Ú¨:4‚ìrgx<À·úW8D@À¬.  ²«s9`vKú®óz¯›€¿ÚÊŽk8ÈŠw— ´þ:°µ MzÏC®™š ü6²›½›À»e<4@Ü À¦ãXX²¹[ÉÞÛIr{ ¸™@ø Çg#»—» äµ< x\:~\µ—@¸1Ì–¼@.û¯äÂ3œ¿¯\´IÛ´£üÊC\ÈoãÇ«Ú Ê¼¶À͟Ľ4¥Ÿø<@8€¸ò{¿¥£8@­€8 üº: ²‰#ÐÕª ½F ±= ¼{ˉ£~ÑÚ­>~]Ñ@Åm„©† så þʪs¡("ÉB`­³Z:Z]"`H œ @¸2 "&ÖÛ©‹ÝØÙ“]Ù—Ù›ÝÙŸÚ£]Ú§Ú«ÝÚ¯Û³]Û·Û»ÝÛ¿ý 6€É  ¶ÛŸ£à:ÜÛý·€Üj`¯B½…ÀÝó}:þÛ×› ßàßûÊõàoì"bJœ ÎZÑŸ*|?œLÔjœ9 â«;XÖ9×À>²`η.  ¶[– ÄC€ÁàJz"(¼àk4D@z1BàÇ™ Àj·Üs·´ ªþÊ~K ~ Ñ#þ¸X»þ ÜsÅj2À`œ¹·{@ÆD ·XìÇ àf,ð]Eà=±m^í#c³YÞÜ—À©XZ˜›Á;¿_¬´™¶Ÿlµaw &ŒÝ—€¿¢l»‰ÝÝ^‰³™ Ê~í2ÚÞz~ Àÿžº$˜ JüŤwúA´û^Î"Þ‰Ñj»o»¶a÷ÇÛZzKšñßõûâàÂ~òÌ~ ¤÷Åê7ØÇ;~ ŒI¾¯×Êí›â±\÷_Ÿù¹ò%ßκ €æ¥Ÿ Rߤ·ôAð[ûï½_®ÑʺŠìù#¼õ›_ý³òi?úmŸæëž îÞÆ {ú þð[Ì~íÿôWLÉdÜFFAÃè|B£Ò)µj½b³Ú-·ëý‚Ãâ1¹l>£Óê5»í~õŸÇx8 Œ'a pt0 $a *t(Ñ¢F"Mªt)Ó¦NŸB*u*ÕªV¯bͪu+×®^¿‚ +v,Ù²fÏ¢M«v-Û¶nßÂ+w.ݺvïâÍ«w/ß¾~ÿ,x0á†#N¬x1ãÆŽCÖ!ùd, X… 1"""+++444<<<MiCCCLLLSSS]]]bbbkkkttt}}}ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›´Dx D'þÏž;:âÓRÑ4 9ê†)N‹$44(  MF< @C¢2 jk £LŽÅ)¨ 0aDŽ "‰Û2 ,ekÆíß§3à ÂV –ˆYÂÀšPÌX‚ƒ•a×ÓÑš/]:MDˆp6&xõhmDµ˜ÀƒšY+hqÄ=t 2bïí$—3 yýS9ο”(CIw?A„èKr)°;Éw4À£y/9G øO¤DÕíGà§ß 40@@ÀÛf A ñ™à1€Rõ¥ÀP¦þíU!à>‰ÐW05a…D ˜q&rµy$ašÑ@*õhkrü@W.V.ùG¥)Ôº©©ÉÑÒ&C§@¿Å•‹-5…¼L&þ~‰™ŸšÀ<#1‰µŸ§Fu;»˜¢ø¶H!e¨º#LO ÐÌÈñ›gÛ^ .cT„FRš+%È¡"ÀÂyl/¼™ŒÕ"±íÍrdòj!=‹ðð&¡DÂ@#0`.s–$Å»š&@@É%œý™ ]¤ÊáêL ¤vR‘,¤F#¼f±´± nŠ)ö4™Vñ̨ve¹GA‹p)¥ãºž%)˜Ñg³‹õ,h£;¼‰–´DȘˆFþž¶– p@–?$À\Ñ4jŠHþ±gAZ9PÄÜúªOWàOp ¹@àOkÒR„„€ª %ØêäZË2‰à³Vù“a¨×`I`KX…£VZ¸….±;#qMpßý<àÂH*Bùš¾–Al’õàR`ø¯“Ä@– @‚3‘è³x‘ÓW|^*4ðÀŒýU `€šXE¥­òá¯3ÈÔä7\²Ÿ:‚bÚk‚ѦdÁ!”” €4•9 xèõlü¤Ø#IùHÂ䘽ìôÎf@0™êù+$^ÞH–t¦#Ÿaóüs³þzÚùÒ ŽIxV„§bŸµªW¾$  Ñà Ë>‚ZÛúָε®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj'û g4j 9#䙓Ç|,"ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~û»Ü“…FMq¹i5 ½UÞ-eçií†;üáºFÄ'NñjK¼âÏø²/®ñŽ{سÜ24ðÃZ€Ö¿›Ÿ ­¼IäP=x6] šSÃæÒ̧q™'þî‚ﯿt®éškçÒ@:4ˆ¦cCéЀú3¤Þ §þ¿ÃêÖ z3´Î ®+ëí;5>vm=ç;¿zÚWÍöCˆ}oo»Üÿ÷tÔ}îxÏÃÝϱ÷¼û}}/GàÿNø6 ~‡/¼âÏøp4~ñÃã¿1ùÈ[ž@k{æ/Ïù2T¾Ÿï¼è£úm”~ô¨_Âé³±úÔ»¾Ä›‡{ì_Oû)´þ·¯}ês_ Þë^ô¾ŸFðùáGÃøÄ‡<òŸ±üä¾ùUŸ½ó§Oè3ÃúÔŸ;ö¿.ýìOûÉ¿÷U-þc”ü—>1Ôþ2³ïo`ã úËŸ’öÿEþï?·ý÷Âÿüg¸ €OQ€¹€€h xþ ¸€3ñ€µ 8 X-‘±À¨ø !ø'1‚­`‚$H(¸ +˜‚!Ñ‚©ƒ.è2x 58ƒqƒ¥ ƒ8ˆ<8 ?؃„¡@„B(Fø Ix„±„à„LÈP¸ S… Q…™€…VhZx ]¸…ñ…• †`d8 gX†ý†‘À†j¨nøqø†÷0‡`‡tHx¸{˜‡M×}|ˆ~j}˜…8ˆšWtìpˆˆ({Š({°lbÖˆ1Áˆ† sôöˆ”‚(x0Š¢8ФXŠ@¸‰,a‰… s`°‹²8‹´hþŠª‚¸(®X‹¾X‹·¨‰¹ø¬H½ø‹È‹Á8Œ ¸‹Ž¯˜ŒÈ¸ŒÌHÅ8Ç(¾HÕ(×(Ù¨´ÈÝhÎá(޲HŽåèß騎ÊxŽíøïñ(ìX+qtò8ŽôÈá~급Ù)Ž ¹&Ñ|ðÚ‘©‚ z븑i¹)’/’¦×‘9ˆ’ 1’zP’Éx’0Iƒ*© 499y“Mؓذ“¿h“>™ƒ@‰{,Ù’DY”!“z—”¹”LyN‰B¹G9•Q•wp•À˜•þZÉ…`I ^)/–T9–•)•h‰„j‰vÑØ’¶—où\ie9‹ny—?y–æ°— ˜~yY‚‹}Y˜Rh—KÇ–ú蘌™‡I‰é’“©• x™’™™ö°™rp™uI˜ ¹¢¤¹˜§)–¦‰x ‘ŸÙšòšp°š³I›ð`›o€›¯©›Á›nà›À9Âix±y‘¹Yœ‹¸œÜ7—JéœÌiwÒ‰ Ä9ù‡Ð•Õ‰žø›Ï¸mÙÞ ›à‰ŽÉi’äYžÏxž”—ž5¹žìùžîé ×9ŸW(Ÿ§PP-Wdâ'ätŸøyÇ “þxCpDð#´¦ˆZ Á©Ÿ©Pÿ©A/9¡†Š $¡D0K Ÿ;†@û³äЦ«²E›´G+KBk{K³,µ­µþ1¨³R{µ=«µ˵¨PµR@´Ñ™µb Ždkƒ^Û´X»¶;ضc»´6º³`û´j+·ÿ¸·Ë`¶¤—·iË·@H·sk·£úµN;¸„[„†K € hË~Û¸3ù¸…‹¸´ª¸qk¹Jˆ¹¢¹O0¹ãY¹žÛ• ë¸š ¬p¶§Ë S;¢‹²‚K¹¯û„© ³Û¤‹¥· »¹û¹« ­x»¸¶û»š»_;¼ìÚºz‹¼É¼ž°»LлžiºÐ{›Ò‹»Ì˯ÅÛ¹Ùk ÊÛ±Ýk°_`½œ¾^¸½À[¾{°µ[ºê+¾ì;²îk´Õ¿¾;¿”0¾ð{¿2«¿×Ë¿ý[þ¿Ñ Àßëº̰œ…o ¿Æ+¿ ܆ Œ Ô«zœ¾ÌÀØk~|¾|­ÌÁôú¼Î˸#|‡¼¾̹ œÂŠà¿ ÜÂ'|¼0ì²l ¬è+Â7ŒÃ%ŒÂ ÁûûƸÂôKà ¾FìvHŒ´J<ÄLÜÄ[›Ãë÷Á^ÐÃÞJň ÃYŒÅ] ÅåÊÅNlÅİÃI ÆLKÆÆøÄÅ_LÄÌÆmlÆðÆ\ ÆwKÇuÄ6,Å/ÌÇéÆhˆÇ˜'Ç,ÈøHÈ’€ÆH Ç‰«È‹lÇÂàÈGÉ›+É}ëǼĬÉOIÉõgÈZ€É¬ ÊÉÈ ÇaÂ[þŒÊ©,ÊÀ`ÉF`ÊÄ Ë±ÌÉE È&ŒË¡¬Ësìɽì˨+ËúGÊY`ËÍKÌŠ̉ÌËBÌÌ–©Ê ÌÊyìÊc,ÍzIÍrˆÌX ÌÞ«ÍÛl̾@˰‡È>,ΜIÎÿçÍWÎæ«ÎÚËμ`ÎaŠÎ¯,ϪÉÍëÎVÏï«Ï½ÉÏ*l͇<ÅÍ^ÜÊ]ÊØ¼Æ œôL€þ ±½Ç=œÍö¬4É­Ðí² ÌÉ!½ }Í%ýÍ'}Ê)}³ª=´/}Ë1­+y-ýÎ7½Ì9M°3-«=ýÏ?ÎA­¯C«5mµøœÍIí°#ÃM}¶GÏþQýµKí«EmÑO ÑY­ÕΜÎÐüÇaݱ[M¬U¸_Ñg-Ö¼ÚÕ6ÝÖ ýÖð›ÖÎ*×NÐv}ÍxMàÒ Ð©ËË×ýÉ}íÐ-$-”?ÐiT»Ö’{Õ½Ø~ 6?2§|VÙzmÕtÒš½Ù¿ f!sNR=ÚlاmÔÙbC2×®­±°}Ù¥ Ó³mÒµ ñÂKðs¡-»–=º˜¿Á {Ãý€¡! `¢-Û&ýÛ8ýÜ {e9äíu&ÝÛÌíÝ@ Þ´- à ÐgwØŠíÐêÔìíÓÑ ³èM»÷ÕùmþÕûí M£ÿÙ.àÀá"IAbäËÝ.}àÎߢù€áðY€RÊÝß¼ÛÜSµJhÂüéŸ.zð•aµ¼Üþ-á ŽÁ>ܽa:²â[Ãrp$ÁRà1Gá#~´+ÚÚOÀ£I°f=®p âùKä5ÎÆZ €ãî%¢Cðm4SCBÐÎÞ¢¹°fî)$åJޝFަPŽÁR>ã=åzPæç 0§^ê+6SRvöå"nç\¼Ó&ðç¥vjiN£qÎÃ…Nç6mè è­kBès.é Î '÷éà''ê}žå<M|ægaþ:¦eººaþÈ‘ÎéL°¦mú¦»pnIrªL;—pŠ:Ìq,ã²ݽàÆêÈnì¢Þ|ê§€ª8ÐAUve?"§éÂ>ìMé©xíÏcGBtg¢/Ø^ßÚþâÜƱžîù»îv@P[€áÇñáÙ>×û®í¢9\£–áÿÛï¤mð².š`>WB:£úŽîŽðœ.šp;gúÚî°¾éðNãÀ`íp”#HN°Éïþñî.ït0·Q?£ÎóÚŸÞ5Oç _!€°h.ñ>íñ*ë,x·AgÑòg }ó!NôE¯îÀàUþ% mÛ-ôF õQ¦G/ ”VOóXïÕNŸà¢™µ(Æ#öÀÞÊZ¿õ4Úõsƒ¶`ØAßö×üöp/œçs$LVðcÏïƒïïE¾"”±ñe/çoᇟ1ˆòh‚¯÷‰ù[/šéûÅ'ÿâ|¿ù‘O–oòïî¥õ¢YÝ=úçÜúàèçmû(ÿúEû.Mû÷¬ûÁíû> üÍû*OüFmü.ü³­üÍüCîü§ ý6-ý`~ú"kýV‹ý)÷Û>Öù\Ö þá×Ôüé¿ØÜoÕÞüßþûþëß×òÙô_øÃ~ÿÌþÿšû@™ˆEã™T.™Mç•N©H€bÑn¹]¯¥"¬F¯•ïùŽÙm÷—ÏéuûŸ×ïùÒëº/Pppê* QK-¯,1q‘0Rr’²Òò3SÓéoÓó“ Ëò®qô¬t•µÕõ66³S¶¶Ò5U ï4·KÕ6Xx˜¸Ø8’öXy×÷w×ÀÌ™ xù;[{Û5™û»©™ZÚ®wÜ\}½Ý=¼ü]]=žîœ:]~Ÿ¿ß?ÖÛ¿lôòÙc&mÜ}6tø0 ˆÇ:[ŸEƒ9vôø±H@µ*úºø&£É#Y¶t9oåKP%st“²fLþ™;yö„%Ò§'š¨l¶ÁITgP¥K™ÚÔÒÐQEÙšjV­[Ç<åJHª#ª]&ƒõkZµj½®åöZ²ÓŽu{o϶y£ª+·ŠU±€ù6Ìqïa9pI.Tö¯DÅ“)#v\Ù(³v©Ž+shÑòî¬92#Èõ@›vý[iØO£áü˜.ëÙ»y“Ý[Im]­¯.Hxrå³.+ž¦9Ï‘/·~ÝitàϽÜΜû8vñã³W'O„û3ó7k\~üÇïǧ¯¦S{•ôå÷÷o?ÞìS(@&¦³­Àÿ´î7ì$‡¿ÀôË)B*JÃþ 5Üà KXD¥¼îÁ³*ÄÍ,U€Å]|FO ‘F†Fd5Ýx™©C¡ ‚ …’È"+  Á•\çÆåJô.ðÜSMÊý–¼¤&Ë1¼«¤Ê¡Ä’Lp´LîÉ$“8p¸07S³Ì8}ƒÓ´4}”ŽÇ«î¤-ÏÁö”PŠèÍN7SóRÌA]´EC+Ñ7ÿ„ç˽´3·ãrÊH¦R=1ÕLG1ƒÔD'5°ÏÏH}UM{CÕœV©3TGXutÕÙh­ƒMèz ÎV‡ÝYIdS+=͵ÖPýL–Ú`–Ýí×{ŠmóÙ.«ýö•k}mþÌn;MURpÕmE\ز=HZWÍuvÝz7i÷µw‹‹÷Öy˵àKðuMß8‚íÎÔƒÕ ˜á[L­¬`Œ¶Wo¾Xë$×R;F÷S9/ìä’Aøã=4&”cQ=vdhã¼"Æšm~8eV-b”&FøXù5vQCŒ<ÚH$ƒÎÀ¥YöKfm‡æ6f‹gþya¦wtšç–§}ùëªÏ•Sáû¸ÖZgœ'뙽©)[^@Ë&ðl­w~Ô븣Uµâ±¯vh´#¢‘m£°6»ozÉ>œnÁk­â¼û[q`‡°èËUt\[È)+¼*ÍÇäð¬å]mŒï>Ur¢ážþ|ïtsÎò|íÖ©Ž=dËKG|vÔqW=õÃ@Ÿ‹ï×]?ý÷Æm/nxãu›òyšLÚ7^âè ;^Âæk¯þãÅÅßž{ö¼/ üÓÎ=¿÷‹ÏWøô»'|úÀ•ßÝ÷ä™÷ß~¡[_Ú‡"Ù‘fæ`%4À¼ð; ”š³W? P³Úx½ VP~´×ê"§A¿q°w(Ä–´¿’ðsù3O/ ²Ð‚l`oh°Ò°rejáüÃÜ™0ˆ6ì`ÿ–(Äê03"¼ £ÔC‰…0sX„âi¤è*âI‹ ››¸Åütq-_$}F2¦þÐŒð@#[d<1ê‰pÄ¡ãxF–q_ll› ñÇÇ>²jŽiQcü:C‚°‘‡4¢LJÀPÂ`D‚#,’R‘ Ÿ(4ÄDVk’/Y€4ð`IÐ4‚`@H´^WèÃGî‘”fLeK:Ë",`²Œ (YõK_Ó?¦<$žNéÀÀÀÉê0s ÒÜõÞØËia˜,9ÀŒd ³ЀäšUä3÷¹$qž‘®)Œ0` @>àžè¬£óî;^~˜ýhÚªÙ´E(D€|rþ¢ãâ.UˆÑ+ýs£k"âHÀÎ"¸SN`6%ªËò]´†Ôh`S”v”!€¼©Sž†HÀTàSþµ4¨üÔãKŸ¸Ñ 0¬ @ÀLARLM™N(éI{šO0ºuC-$\˜N–¬RPj,MðÊ ðQ¨ZuŒ@M¢P·êÏ®=dI%/™É"ø•0@0ˆŸru$]C)Z²‘EjVÀI,g^‘´äAmjíúÀ”¢O±,Ubc•4[ÈÖ/­•ékÛÛú<ö·’âm]ØLãºö¹B®Q»\Äâ«‹Õª9{;ݘV׋þÌM˜w‹kÚù–ºÊ ïu-š]Ý2–»D%/@Á›FñÖ ½°5o)ç{ÎúÒ‘½Ë[éOÝK`HîW˜êµo€¯ºÛøޏò° àè íÂ(pˆò»Ñÿ*ò¾‰{ïv]êØ K²Â f09sk`ø–¸»'îㇿ"\ #8‚8îO‡é›â‡xœú«¨€ÿ–aÇј+6Ö°‘¡*cþ:9Á«…Š’% åóVÂLF’·Bå&k9mVÞq§Ée­x9²b~«ŽåÃc K¹)h3˜Ý Ž‘™ ÏB@ÈŒb83EÎuF›—A³›ºE†.¡ëêã$yÈ ~qV3…¤]ZHJþ#IŸgìè.CzÀ l±¨÷!hL˜:Žff-¨GØ“š4œ˜¬£üG,C˜Î´ˆ®)êZs˜Õ¯vµƒaÌ$^OÂ×[Tõ”ƒMlJ—÷Ö¥R³²Ž}Áeǹ֮ٓ~£ýd÷ºÚV´ˆ²Mâg’Ñ‚š62ÂýBOŸ¹ÜÜ~ðhÓmŒo#»Ýö»6 ã íyºÛܸ7µ×íîq%Ðù§Â‰1pv\ßï^õŠ…êak{Ä•†xÆþ¼}/%á_tÀ·áp°tÜv'7ÅgØjì:{Ûì0yvDîq‰3›åv´øË1îbwÌœã5OùͱsŠî¼½0ϸÌQ®²¦™èü6þºJ]žôžÃú@„Öm~pŸ„<×B·ÖÓEìóïFäýF7É Èö]‹]\º×õ¢v\×{ápß´ÞƒÜòÔrÔÖÿ%ÞG.ø’“½ï:ÿ;à=uÜ ›ç掹±ùiÿ]å·;½Ý®ÏÎ_CîN¯<Ó2ÿõÍÜðmOýÛÁŒt"cí+ûì ?ŒÐÇñv‹½æßܼÓþó…νë%^ºó쵿qð•qû²cÝø/öÝ™_ÚêÛ{øU=mwoúÞ·òΗ…ø-_ñÅ—¾îßÇoîÉÿ“ìCÞêÑo<ðWïùúƒþýJ7»‡»Ÿ~úOŸóîOøFOûŠïìŽo'’ïþú~OùÆŽáoû’ eB‘ÐÚ¯/û&ð%*Q/.ÿ®îâäï´Noð°ðFððPÿ ï¥ïܨÏ­ï±M0òfpþ0ðË€0ÍZ/\JPò–®Ç<Ð%@°ÐcÅåí •­ÿOÝçÿšÐÛ¢ÐߦŠÐÏ ÕïùNP µwð÷/ ipòlР׌m ×.Ó ð Uï ã*…A!ÐùO [‚ ÛÐ Íð‰9PÙ Ýõ0YOËÏï:ÐÕ Q'16æp±ÌªÅ0ñŽnͯÔ@ñ9±Ó‘% Q;þѱ‰Qð¼òðÊê°=ÿа kñ×RÐ-QñQcM»­)ío/°Pc 1eЉ±«Qݲ‘ãÏ1 Á‘Ÿ±ùX‘gñ»qéPë‹°é‘ N{ðá ï±Qò15qaq$dÑ ßq!«àÎò Ïö —‘þx2K¦}q¸r Ñ>rý²r2"KF"‹#?B!½QÕO€Ó.MÓN‘ê’Éí#mïPq"/±#0 ]² R,2ídr&¦&ùl%=¢%çñ(©r$2 ¯÷DRþ¹r‡~P+ƒ0,‡°ÉO)‹Ò*YÇ+¹¨Íñ±Ra’9rùèRôrRóÖr>Ú’(‡‘$Ãqíq,çL µÑYF/£ˆ/ñ%ÿråÒí²Êðò-‘R-)S}3ý.2q°p0ƒ2Ç%1C!*;b*³*Yó*-s%s ]ó2 z4“"Ñ’6c34ñ3S6mòñL3 '5-(SQ(uÓ-³3Ó1ñí43r:ùä8'b5›³5µó5Ó’73芫ÓÀ28 s0Í’ýÜñ;s¹ÈSŽps996³ò<Õ>³7í+?î:!";û²2½Ó>÷³þ. ô.m“Üú“Xþó!”1·S@³>ã2: N<»2C¿Ër=ÃïC7´@Á3¼T¦Ô! t3t7GÔ=}óBND ‘=ÙÒ<t2=DwtF=3FmËD›F>Ks(#´;[ÔBŸs#qt6¹³6GÓrP´!T47é“@“´B}TI1TAó²GQsHQQç“E´=T?I´ÎÒK»Ôg¤ÔF4r.™”,Ó3DÝ”B¯T1ØôMÃô& ÓL]MEó>kKõTz‚ô¨üt8‹tEóIµ4K¡sKeO­PQ% N‚JÉRõP#•R'U:¿”:MÕ:Õ÷`´RASMþôU—ìRT÷ÔPóFc5Gí”GuU#“Ná-S¡jSÿ¡S‰”9'TB”!I•K¡Û†UµTüÒqPYÕYUW›TYñFZo4*é¡,K Hàš<@¦’sL‘ÕJEuI·µNÏ“4Å”8g•,ŠÕðÊ•Œ JÀ`<€@]QŠ]µ[›QãõE;_Ï´UƒëVÁt4Љ¯L ­ v` ´xÕZË'c“•Y%6[ dßµV)Á$O²C$re?O3s4”*›¶ ÀŒà¯ÚÊW‡6d@ÊQ«´Lv ŒÖdõ^v’'€ig¢b©É4dþoŠ– žŒ ˜ö°F6eÓTq¦6iOVP'– ÌöS•vÖ˜²)‹ä)M€mŸö[}ª  Œàà¬Â–h Õê–VÝ6T[6 —aáõY¡ÅqO­jSu4>ª `¤Š üpaŶaçtl&7mÇ–Pƒ•nÛÕ^+ò‡L7**·@ª©º2:€fÉ8€>¤¤ :àš ƒ»g©À»F œ= rÚ¶ ãg}ùn)‹©¹¼Ï>( þ“ð30 é©L¹ F‚˜9K˜Á]$F€  @n{+ FB"˜‡ù“C\ÄGœÄKÜÄOÅS\ÅWœÅ[ÜÅ_Æc\ÆgœÆkÜÆoÇs\Çw< 6`³ €l:Š‘à:€Ç“ܪ€î{íB Û²@É«Ü0Òš 4ÜB¦@ˉಭÌï–Yd»z¹ÒÙJöÒy$`–l˜€³:ødÀÚ¼³”Û¦1à*¼lš¥;Ìz@_Ä­ÃÒµBf @ d» `ÖÜ‚<‹Ù9“:€æé¯´þ ¼é<¢ºÀZ]Wi2À{µmz@°DÀ ø*° à+¨\g˜<œ#ÝØ¡âž‰À̉à¡Y„ݬ±yªÿÊ $Z‹=Ë ý•Æ É‰@©@˦=šÈ•Ý©M …‹ Ø]Ý›"ÙMÓ‰ ²vÛAªk‹ °ÿj–† >¤Ÿ³]"éüscɦ­½¦ýU¶‡y–ô8Ý×á}Ë—Ýô¶àÑž:ö¯d›E,¼E :´k{šo:ÐCþЛãÜË…½áWþ%< Î â{Ù k€ieß—C¼ýã¾9–D*äùJ¶O~­-žå“$Ú=晖≠Úò‰`#¼àáÚú¸ý²"Yè‹ÀÐÉ›9)hë]é˾#>àå‰àÝ#Þé³Ô9@ÔðÕhêWÞP€pÖ.À p5óe`@’Ô Aþ(…r„é4ˆ„À@Ux@„O"4 @0•á†D ˜l9Ðâ‹  GÂ\ ":€žXع·[ñÍ·âN!4€âCl€@U`Þj;RÖRA ÷™ Ÿ ¾8À¯‰Øåß æƒdzX^xn@â›_òG‘@áB,•Ûɶe hæFh [c ,ö‹AA”™áLÞH"Ž!¶Å, Áë}§Ó•8‚Œ€JD™¯ €@• §x€“BT7bdª„lzÉ—Ä©º®ºžžBÄö`h,°%[ èìzTbz-§¸V‚¤þPj)©Ýîê¸Cˆ«)¹7~€©\ zÜzû®²~N$¢H`pÙ•„ç¤&À‘FxpÀ¢h{„N¼–¡d&|@Ú•€ÀÇÖÊ¡¨õ©Õ–NŠfWá±Lã‡&|Ê¡‡fâE¨þ ü‡ÄÈXáÙÎH९¾è»±@µŒ ÌSèhwH³lkÍ á1ÈBˆL2Í"qÉk]2cçèÈ¢–}lÔ[g¨`ÀÅÆö{á•b•n‡]){Itp€vX|Dl} Qét@­×c·½µåþÄT}l °¨gEë4À¢Ly|¹ 6&jÎ,ÀÝF ŒDþlÇçíxë—fŸKnBäE Bå:­nÂùäm£L¦›pâtl¼ò¾ñõg‚h}òx?ä_vOÀÁ„“*À @| (Á%t@™I ?Ó#ÇO–o%¨œÍj·••JsÙB’Ët²7š påâXØp ²;Bu~²4ýÙå=FhÒ³HI(3œ b™"\i:¢ ñ32ý5­iý‘…02!d€ÚÏ“vh‚+…Ï!0Ô”P6ðñlBh€b¸„óÝ/ ¢ÁžÚŸE™à}Ú3Ù­œ‡@Ö¤s3AL…FúÆt'ã^þ‰@Â!\è† ^Ž&ܵÇ ä_vGÄ ñ¤‹.䨈`(!ˆÆ‹"ê´¸ÅE¡‡&xdÀH³NqIGLÈøÑ’%è :`x±™yåPˆÊÊylfB€óF¤2ÂhR³$ÍnY]J²¿<žñ†P"ª`¶%ÂßÔQ’ -oµBO6ÒÛ [iË“áìcó#†R”<%^$K˜p #AÅêiè%xDØžðæœÑŒllï˜Óüæ8O·ÍsÎóž{;hxކ íH[yº“3; ¥ £ ¸®F©©1õiþT=Šú3 ¥Eì½æG7>BÖ³qõh”gwÆØá±ök¤ÝooFÜ—ÑvwÔ]êÚ˜û2ôžŒ»³ÃïÓøx5O ÂKðê@|²/Å£ÃñŒ< oÊKþòv°<94ùÎÃóâ½çG¯уÃô¤O=Pï Ö«þõ^p=7dûÚcöÚÀ½íw/ÝcÃ÷¼>€o â ÿøF0>5”üæ3ÿðPo¾ô}¶WúØ·Âó±~ýì{? Û‡Fø¿¯úñ«½ûäO¿Ìß ö«óî§;úßOÍñ¯ÿâóß÷ûë_ýü‡ øµ6€Æ`€HhH ˜þ€”Õ€ÂJ 8xs¾ ÈÈ ØO‚º@‚"h&ˆ )x‚3±‚¶à‚,0H 3ƒ-Qƒ²€ƒ6¨: =¸ƒ'ñƒ® „@HDÈ GX„!‘„ªÀ„JèNˆ Qø„1…¦`…TˆXH [˜…Ñ…¢†^(b e8†q†ž †hÈlÈ o؆ ‡š@‡rhvˆ yx‡±‡–à‡|€H ƒˆýPˆ’€ˆ†¨Š ¸ˆ÷ðˆŽ ‰H”È—X‰ñ‰ŠÀ‰šhwþ—žø‰ŠgФhj£x«˜Š§¨uø‡Š®˜f­Xµþ8‹›'‹åp‹¸zº˜‹°Ø‹ ø‹ãÀ‹Âø ÆÉxŒ³GŒ¾ŒÌˆSËøÓ¿çŒáPÖX|Øxzݸ7¡“÷àXâ¸çXŽâGŽÝŽêx~ÐøŠïø€ìØŒñ8ˆáŽx øØ÷XyõØ,Á™Ƀ™ ²rèhY… y÷©Y2°‘Ù‘ù‘@Y‘ q‘t `*¹’,Ù’.i0’$9‡2Y (ù’8ù’1I‘3i&97™“B©’;Ù“"ñ“r”C™“Ei” ”q ”K©“5é”}X•Ó •SÙ’Mi••þŸ)¹•TÉ“^ `ùZI–D‰•géiékÉ–]ù–—m0—dY—vyxÉz¹•|Ù—_è–Ü7–lÉ’ƒI˜ñ—k˜S¹˜ŒŽYzb™˜\i˜“i•™¹”’¹™Ñ™pq™˜¹’¡)šn¨™Ïð™C™šª©¤y®)”°›1›fP›LÉš¸ùºY¼‰“·ù›œ«gš§ “¾iœ¥h–¾ˆ˜§YœÎ)È9ÃY–Õ9šÍÉ Ùé’Ô¹ÿpbð™ âÉä橘ݙž»øžÊО¨)ŸðùŒ›¨œÓiŸ÷éèI % !rÿ¦E§DŸmùþŸýÉ™ü¹ " +‘³oÒ¦uÊœ º –Ø €&FG )‹öJž‰Ê $¢F4ñX¢)z¢×¸¡Ä°¢:¡{! QO'˜i¢2Ú¡4: +*¡BàÐ\Á£0:¤AÚëyEêeCDO¥Ö£ªP¥N@[C¦Ÿ?Ê¥Z }P ¨v£H¦D pš> šdZ¦Ë° § C×à¥Lp¥a:§tY§v ÖÐ ) À9þ¶À€M¨Ë™ … —„J ñ;a9vUÁtÕ0bš˜@š©Ï™Ÿ‚º—›ªªþȪ—þª¡°ºSúYZ«ùp«±wªƒz¦ºš¯Ú ¹¬ ¬×ت‚9¬Æ̺ ÅÚ¬óÀ«]­ÒšŸÈz Öz­Àù¬%è«®š­Ü:£²z©©:®þY®Ëy®èÚzÞš ÛÚ®‰÷®*®Ë*®òš•ôz ñš¯ç@­Ô§¬‘¹¯þšƒ[ ýZ°À¨®û‰¯ k¨Ö'°tê°ëKƒö:°[±óy±³°‹Œk°ûš#²§°[²(k ;¦۲вZÀ²2«'ëƒ;±7[Œ9û 6۳ܳëX²¶ù³B»†HÛ A›´úJ´­¹³& µN ‚K‹„R{´T[µþߺµk´½éµ\˯W» M;¶Û­`Kœe‹¶„ض©p¶n+bÛ±k«sK®;«ìš· ·R˜µaë·Ú ¸)+¸l[·„;„†{…ˆ‹·‹û´/‹ª¹ˆ@³Y ·–; ˜{{ ž•»¹¶º£ ¹¢k¤†Ÿ{žbPëæ§;ºŠ+€«ëžDë07»±K›© ¦ b ’ ’"©»»+œ½k†µ[Ÿ· À·É{º{¿ûò¼æ½¢;½Ú·¼˜ÊžÎ ½Æ{¼Ø©½P½Np½âK¾“h¾pè½´ ¾Ø»®î¹Ü[èÛ꛽ã˾`p¿T¿Ã¾üëþ¿˜X¿u¿}«¿L¿ýkÀÕŠÀ™ ÀK°¿ Á(Áz¨ÀšiÁ ‹Á‰ÀS@Áë×À ¬¨Á—@ÂIàÁ0‹Â)üÀÎz· Û¼ó{Â0L"Ü{lÃë›Ã:¬ÂØÃåiÂ/ ă°ÃàGÄòûÃH¬ŒB\ ,Œ.L¹2üÄ<|ŨKìÛČŀ ÄÀËŶ[Ä7|Ä`ìbl½Lü¿FlÅiLQü¶d̼fìÄqŒŽs< S,voü«yÜkœ¾mŒ«®<ŽZü }œ|‡|¯‰¬Ç‹¼…Ü«g Ç‘œƒÌÀuü½n|É€œÉû¸Ç‰XÉ ʈ,Êw°ÉÜÉñþûÉx¬Ê@IÊ‘ÐÈaŠÊ,ËIËŽhÊԇ˫˻<ÉÄêË+ûÈÁ,̳LÌVëÊ <ÀÀ̳ʜ”¼ü¶Ü¦È,ÍÓ–Ì ­Æ\³Ù<µÛ•ÕܾÎÜÁᬵãÌÍ“ʰüÅ뜗åÜ×lÑ,Îñ,ÏÝü­çìÃðœÏ–¹ÏðúÍ™›Îƒ Ð9ÏÜÏwüÏÍ»]¯ íÅüО©Ð‹PÏOwÏêlÑ¥Ñd;Ñï\ѽ›Á"mÈ}Ð%¼ ý‚í¹+¸-mÒ/°1M½MÓ5œ7±)mɱÜÓ†üÓ›ÓÝ;ÓKÔnlÔ‚p. °}Á¥ZÄA}ÊþCÍÔÔçÔ€@¢ñ㻆¼WýËY­ÕàÌÕ$ñ} iÉYÖǬÔ5ŒÖM P4ÑçPšr ÎtÝÅvÁjí "?Ð×¼ûרe<Ø„ øÒGñsp]¾Ž-Óg-Ùø{ÒXpÐ ÌfÕýØ«íÙY Ž¥²Íwž»Ù: ÙvìÚi- à І§ÚÕíÛ Ú—‹ÔŸÛžlÜÝ[ØŒkÛIÝÚÎÍÀÐÍ6’B¾Ò½ÜÔ]ÝŒÜVð`Þ@[FšÞà]ÜâÍÆ×$'L Ý¢F`ag©ð Øîþß~<ß°™ý¯ƒ)Fªtu&%£Ñ¦Êܯ,àKLà€À×å£!IPrr†rö÷ÝÇ-áÏ\ÝY pà¦ßŒ¤iœ†Í >Â;½Ô.ßÀ°°8Ž*ÙÓ6šàlˆáUìÎ3Nã¿pãä¥åtKà§Èâ32'Bãu]äÀkáàäÅTl;nª/ÎÃ"NÞK³ZÞlDÀj.à0æXŽÁitpþ`tsÎäI¦Ipiýæn§rŠÉ#ÃVþZzº§b—á .¢&çá=ªæ`éE~‘Pér~és^éM€¨ŠÊ¨CM&GgÐtþMÒ*-é3°þz;ŸªH&Àß”MSÎæƒnäj‹êlLëµnÝhšðÀ~Þ~넬ë»>Þmn#ð"‹œdÒ_¾ÄÆ~ì-|èSàôu%À¤<ÚÞÿÍÚßNí¦šìuP¹Q}LÞnêBîÔŽ”¨0ÞØÄÎÀÓ.îŽLît0¹Q;£NDÖõÍOâÖ.üe€::ìî>Ý¿ëH ¹¡UÙÒi½íÀ{ïøŽÍú>O…oÂÍîÄmòÿt!/”$×_Ÿòã p=ÐóÕ>ó)–!j‹!UíÝ2éÒjnÌñ¹îõ’”ð/f»‹öØìó”šÝNïS¿æpo×HIÚj©ÜTŸ÷VÎÊãmöž÷hMø%løöŽøZ­øÕÎø/ø“~ðøzOù©nùP,ùã-÷øùHïù2ïøL-ú~Lú=oúDúެúHúâîú¥.èíÎõsÏùaŒù‘®ùNûiîû‡/üñ üîøCžÊKÏôí¬ü§NüâmüýŸÏú=-ý^ŽüIÙ˯òÍŸËŽÆÝý³ný5Mþ¼/íæßÒèû~,ûï®ûrìþŽ ÿÇÞþÚoÿ/ÿjœþþ¿þ@`‰Eã™T.™Mç•N©Uë›Õn¹]¯}Éeó™¦XØm÷n©ˆÍáJŸÑ}ÿ0Pp°Ððð+Œ‘±‘K-/²m¯àN2’Òq“³Óó4T´rqÔ”3O“ÌNu•îTv–¶Öö·H1—-õŽ5ÑX/¶9Yy™¹yh×9:ê·xòxø²ÚºTºÛû<< Z¼œZ[ØËU{»Üý>>š\^ú¼:½k]îºþ`@†è Lv¯X¾GÄøõãfbD‰Ÿ¤h !0…[ö±ÛxdH‘ñ,Ž4•ñÕÇq ù©4ùfÌ\%ezB©Êå•þŽèüÕôù¨ÍžAÝÄ”ÓÊN|C‰6uú´ M¨„ŒJBZEiB¦S¹võšfëW?U3…Å’U£Y±kÙ:•Ú¶Òš†W© M©n^½#ßîÕ@nK¼XYzìqb€}ŸÜÐa¥l†6¶|™äaÌPȪ̱0ÏÏ›I—nÆØtÅÇs5K±‹³ujÙ³A¡¦­¤3ºS^}xpª¿…ç66zådÑ™7GE<¸ñ`Лô¶JÝyvíI±Ó–þf·ëÐK‘o7~eùóßÝ„Ÿ6^«zôóé;±­}»¨ðÓʯÿ@"îË.?6Ü㌿»ü Aút®ÀÈöS޼-¼Pˆþ›‹ð@Õ(ŒÃÔ9»Ã-AØ‘EïN”ÍÄÅû°¿m4ÄâVLÆ÷hTðÆ !|1µ%ƒ¬C!•Ô+Çèv¤ìHÖz\’J¸šÎÈ ‘$²Ê.›ºò¶,[IÑ·)½èýÖ^ ñ½W_aû=x¿tG]Þv v¸\„%Ma^Vu` Žx⎹«øØ‹oÍãÙõe޼Š\0d}–ä‘Sžù=ëe8_ˆOöU[š}Ôf€q&Xç†cW·•Žö_–E6f¨M6zéªTÚ²–“ëyꜭþÚ„¦/ÓÚ1¤#wg°—;ë§aîšè£¹VÛj¶#[§—Çúmº«¶[1¼¹3{º ŸÑ;iÃýðÄ'Œpð°vvïÅׯún·+/Úkž÷µÜçÆ{¼.ÄÏæ{sÐgÝ/ÒUŽþ¼½ÉM/\õŸYßËõç–ûóÚQ¾IÍG›êdw÷½càóÊÝÇãïù‰•·RøÓá֘󸣗xzJ€@€ à‰ %A—êiG}xö­2Î;埄<·O_qŸ@ƒ`GÐ4‚ÈÉÌCìôs½’ ) ‰’à1ׯîQ¤ ÀcØïjCÃïFBŽÙH ›Rá¦:•¿ƒ]p"Ì÷#˜9 ôóÂÅCQ®}÷;œ cr€!È€8 !H ú$—? ¾Ï„ikÑ×'Ä U&`F ÁèÀ20€Hþ1„>ÜôžWÂq±Š^bi 0Æ!”ñŒJA>>0‹Åë!·8»<ê1l`| Ž8„$j€ ˜aé2HvŸ”’ð;"ò †y@h¨M¦ e‰EÏÙ±Ž'l¤(“4³ 0˜ @€$M’Aò a\ ÉI\.ò™ž”cˆJÙ@/¦R"ûÓ€V@üoÈÄ<€7Âñpµ”ZöFxK]þБ¦„d$O “ï…o|C§"`€`ÁÕÙ·Dv.—Z„'/‰y.lzE‡ƒb¨ãiÍ U”õŒ×C»ÑêT4£ÄCè.§©:ŽrÅþ£µ*éàVj!ŒŽòš ^ASçN…Î1š"zi//§QÜQQžîë";yGÂTˆ'MP-jSEJ³“:=*Om'Óå15¤B}¤eÁ©Zu[J…JJ›õUŠN”J;+ÓÖʲ¢­Z *)Í:O±>å­IPëY[Ú ½Úµ­kÉ+þ*צ¢°®¼«[°ŠT§Žª9¥fbõ¸Ø/5–ªpíëë6;"ÊÆÔ§3íló¢Z¶Ñþ§°• ¬X{„Ôµ«ýlRWû•ÖN1®\]§ls Ú Ý6}½=¨ö+ÜûY–(À½šqúT©27zÈ ŠrÝȾó¹§ýštBÝ/Z§þØ,xkÇÝŸx7’äÍ›z;ÛãÖ¢˜ ¥v%Jßù¼–¶¡½*MƒÛÝ>–¸$-­IáÛQùòuÀ,M°KÝ»=óú½øp;‹kßž¶—¿¶l.dÃ{ÓñZ¸ªú¥^†‡:\ CS¼’°ƒ ŒÒ‹4À×õ°ŠÝ/•Ä[õ¯A'\Ô /xq®I„k,cç~È~²LˆÌ^ÈGEöÝ’cÒd(ëUÊH–,EÜ+'Yw`ЊmÜe·¾Ø°YÕ-¼eÐQ&_vó“ÅŒÚ,sÃW&윃Mç!áþ¹ÃOÞs ?ýæÖþÈÏ{ô‡'ÝÞ`WFÓCôÐqÂ^¯:Õ}ŽòUc½dñ¿ÎÙ¥cîc7; ä~ö¢Ç÷åŽ]w²g.nqôï{_Ú‡¬ö¶[ÝîbxÞËNù¹»\ëÁÍür7lËÏKñ)‹:H¦Nóµ›ÞˆÿƒêEÏx&;¾tŸÇ{çgÁz…~ë7pà3;û»ïYö3Á½/†Ÿ<×Wö‘¯{À—ϧâÃØðå=~œ“Ï|·[òúþXžÏ½éºúoþi¯«î;úÖïÕ½‹y?ßß»öü¢Ž¿±Á]fº__üø'ÿûoa{âo?÷î/ûòo÷Oò:nþ¾þÍìóøOóó ÐóÊ/æJLýíb¯üü¯8úØ-ÖÖÇ6ðü<°öpðêÔ¾ÏÔÂÏÇOééP°ð@PúDÑÚÁdÐ÷xPøðÿ(ЂXpátð /㢯æh0 mÏÐá#0 û K.ý a0ì¸ß„pLýdnqÐÒŒ0ͯ‘0 :íÓ<-Ôèokã ¹eô.¢ô”ðôô0ZÍwÐ + åå)"p‘0( ×zí×Ò°÷V¯ÊwÎc0Ëj «‹7+±‘ÌpÖ:‘%ð»J± ƒÐ¦'âÙé2Q³VþQ +PÇò QOi±{ÑÇÐYL¡ e±øŠQÿ²n_Iï»p¥±1q¿°å0ÓLà ßp~â0 Ÿ£S‘ ‘Q •±ãn_Ï[m±,/ ñp±P•ï1¡­yíÍoG®m±Ö´1ýRpõ‘»å--"c¥ »î ’ RwÑ9ñ2ëe"ÿ¢"Óî"ñq!×P5¿oo$Ó-&)&’×±_ò;ò?R%R$‘&·°5Q³û’•råfräœ2=jr#o’)­'UŠ¡2‹ÒËþÚ‘0J²ñN$S² [2Íòñª’µ²ñØòc¤-9R'Ï‘+±.“Ñ'Ër%ƒ2/£’(¯Ò(³ò(÷1) óãÜù³tÀòõÄò'CÒ%ï2³/·0»R1U†1‘Ï1õ(#ó2í24ñ’'í1!©%3Åc3©¯3Yr/AS-sR2qK(=2&5k._ó,y3- ógs'çò}³mrAVüZÓ2c+KS k#ƒqTS5”³™6Ó*30¡Ó+åR8 Ì:f7?³7Ñó7m²0Ùs)¹s-k³ëÊó£°³Ó=ƒs4'<éó9‰s©ü³¬ìÓµ3þ=os=§²=ô=ó“KS"åÓ1TêÈñ?ÇÓ#û?´)%´-=ô-ÿÒ;gQC)³D!t:ïRE(Ô¾Çò H@†<þ 4Aã’*áS6÷“6+s;ô§XTKC›üGƒŒ  `6À pÔ6”GôJ”C»ÓGýE/ËH÷ÌE™!ƒ¼É’é2 šÊJ³G±ÇLm“,]S=«`NíA½!O™ D¿’4V‰8l¨Àˆ œœ @ÓëMûƳN›³K3 %3ÒºÑï$Ô&•ÃþÆt M-‰€ŒŠˆ ƒ¨4"þuOãô@UO+uH·ô hÕR‰t8Þ‘ÕJAW³MT§ˆT•AŒÈÈŒŒ €>ŽéUMôQ_+XÔV­US± ZwNË 0r…Ò¶"ÿt1I£È(iJ`Y›õY«TZ1´'†\e5KõsD•à^±´[snú`…4D1È(U‹`šIZµœU(@b'–b+Öb)`ˆN<`:Öc?dCÖc=€NÌg.e/6c7`cEÖe_–dçÄdS–f'veé^VgA6fådfk–fovN8vg‹¶gAàge…VNˆ¶huöhAàžvg6i•Öbþ™œ–jE6j¹¶ky¶dàd±Ve5vNr6l½¶NÀvm;ÖjÉÖlÏ–e‡ömCökïVle¶PIXiÕP‰Q•Q‹À||Uq—q×qr#Wr'—r+×r/s3Ws7—s;×s?rûT1ÊtÐÔÔ”ØÔH`¨S_vcWvg—vk×vowsWww—w{×wxƒWx‡—xc÷6}Hc`›þÇ›V©Q›ôI£4wž®{§Fà ÕWh4|n4{Ë×|Ï}ÓW}×—}½a{e3œÕÕ£"`@tay i ô¢~ï7‡àþ{ %QõÂÀŸ€Jö·ú=’Ôy7ÃÌÈu?óB`þçˆ`n”œ4yábƒ;>XŸÀ€äࢠ>€`J… lÔFxJx;Œ©t ƒý¢‚¡u3H …‰ À~F "1X……À71|¸8˜Š™X ŠÏCPW“ÁY3¤¸]ßµt x/ÐX€­3 ×]™uÑã`Q“.£‚Ã'ÀzÃM@”ˆ" †X,ÙªX` À‡ý‚Vu—h ‘ƒ#]ÿ(YÇ&ÖH \“Xʸ]µQõþB‘3< Ä'“×¢XØPyTù< `•]Iƒ ’#qYt¹ñ7 õ—õBù–ã5— ’ƒ’6M`–Ù"+¹Y’M Àã™Ù5‰,™ŸÄÙ<ÂXW6V©›×"ç˜pÙ˜•ÉùÌÙ/žyŠ…@‘Éêy6€X8H6‚y˜K9 —¸‰Ÿ8Šù¹•ábQX‰¹X@£ÍC“šWš/Ã$€> ”G¹1:€È8€òd :€„÷‚¥]¦K`dØ:À Z/u?à€ 5„kú¦ÑãžÄÇŒlø‰ Xþs¡¥QøŸØ/®úPF€  @ ú) QžÁ!¸}ß®ãZ®çš®ëÚ®ï¯óZ¯÷š¯ûÚ¯ÿ°[°›° Û°±ƒdü BaE×. Û²ó‚€Œzæ1Nª¹Ÿà²G{-Œ Ð PÛPÛŸIÛµ§b€ ežy¸lØæôlx$`€V™àŸ`ª8–·MÀv 4z1àƺ9Ÿ9jy y¥—_Û»B@ .à±…  ·=À± 5‡Ç§8èpK`Q ¦…Žª¸^G÷gþ2À¶™Žy€œD Œ¼É¼ àÊ)D;l4Û…¿›Â}¢ˆ…€¶…À¢W ¼–KX”錾xµŠŠ—T†©²…`•jõ9²1¼“Mào‡àÁ+ÇkâÂMÀ¼‡ N5ÅýhU‡à™Ãi€R×–˜Å¹™„^hI\—Ô¼!x€’÷Æs\ËM—3ÜŽu »³\Ô4œÌ{­ŽÏÆ ù¹A8aŸ¹„[ !|Ëïü"< ŽÉ˸ÉOU@Õ¼üY턊×Üɇ ‚à\Á©yèÜÈÏ+"v¼ÏAUÌ…àÃ… I œÞüª¸T\Ÿá·Ñ‡€ºe¼„ͧQ‡ÜÒc] p¹ÇÑ[ÓOõ½9 ¾¯ºQȉ"@…á™à¿|ÆM Áe„$ È[Ö«]ôœÏÏûË5½»oY¸ „:`–J‰Ý”›¹{[Õ—=ÆyÒ…@ÒC@¸ Û­ß_¢p§`ßóÝß ”;›£@¹ÿá÷"Æš{%» ú=á#^â'žâ+Þâ/ã3^ã7žã;Þã?äC^äGžäKÞäOåS^åWžå[Þå_æc^ægžækÞæoçs^çq%!ùd, X… 1"""+++444<<<MiCCCLLLTTT]]]bbblllttt~~~ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›³Dx D§þƒ!>ø %h%<Ó 4©£83’ÐÀÀ@€4$Ù «W¯B4| ©;‡ uº6ŠY6   À@„Lº@D’·hhŠêÙ´Q3f0À4@â‚e –` $fË$hæü§„=›0:µS'¬ÙD€A„hsX¢×_¼HŸLøL["ÂWôÐȾ»› ÐÉôê^J9~„; „ Â!€Jh)ÀopÕg vÆ»‰äÊ5b¡IÆÚñSRxÄ ÀGDV  K ñA\jDþæáN!0på€ ž˜\ á|÷í„¢‚ ŒG Pâ_´WÄb¤À^8hR"0  ‰Fl€ÀU``àZ3pvB@¸à„Æøš *È`k^Ž •Xf!% ÀxciE%dhæ‡Ñ~4A‹Iì™!%,@æd€„W9A ¹§„pÀ†,ºHD—o}Ȱ€˜ZO"Á váŸC`Ê©¦6€!H´YxDùÀŽGô¦a‘<.€¨æm@ÄlÀê´¬8`°B“Â(œX(ÊcÀI+Àþ·^KÄŒi+)qìyh㙋®µ¿ÚYQ ΛD Wé:ÀL„ º´`™,’& QÂtVúbq€q‡y ]Úé„!rªi|©·]a0Ä´õ'W!1VGàê2]…†Ùg‘œÈCŒ,@k,Hæ¸;›l© lf!Å­]^q&„à0Ä’ ýtÄC\X±Ó͇V¯^µÄúF4Û[IWpÑ­qÀð³¥)b€ìâ<õ|>-D—Çq•Nh®Zé4€ãªžL„|BÁhZ8ŽUý"1›ÌÁŽ5–Ð16>0»Êš8ÿ™}õþ¥ñzسo®ZßEÈZrî‡×n;­NÊš›0€ðmC´ßØK\—„ôLp€@ w;^DnøÚt q@ÁDœ1Ÿ+s¬Ú‡ !VžhÁ½"ú­²<ÙÀVQèùòÖepÔã+ÔI«4__†õ»û áI tÅrîýì{ZH¶!)ÐHæ;žZºF6ï!@T´BÆ<¸žœeº® Ç€ö@8Ë sÇÓ”‰Š5žFwN)ÜÆó¥g-ñš\tf‚±ÜPX—"Ö{Ð5ÄäP{ÓñKr¦#gíeN¡a Ñ3FšÀŒú›OrþŽHÆ6äy*ìýŒ°'Ã-¡Ør£½í-p`³[U€F.ΉDHàSæC!0kB€ä“Gðp0‹þ*[¥Å#ï‹ ‘X„:ŽO‚«¹¤ ™ûhf¹eå/µÒvLÈ~ ­ÆtÂH°r{JSXG„ Íç?hÓ ŒŽÓ1ÕpÎqc¥sTóŸ×h“Ul_úÞ¡’bB"’gNâ OŠçŒoé:H‘EB4yô¥:ö³x“m Ì`fÄ4¥;g&·#ÐM™¥Ê3³ÆHX–à†`bdÕr3dÍbã ¨‡þV³‘uΗ(sŠI«–Rtrê‰G&ZžóÄÓ9DÝÇZJÏ©  j²jZ/M(¶ª• k"è¨>*ÂânlV+©&«jƒ:´ \Yh|&¸ß”:ÓC+>`#e nCØ€yl% šíÚV·^Ö®n*ŽÇÖ÷,X¥ Ñ™äk`&!¿6 Cتò8Y„Ùlæ6¹ @t’0ÊÕòH¯BÀcDÕÉYiËY{³A³©½Š°r­M  »&‡ŸÃk–k»:ÌŒ‡¡_í‚üº „ $ “¦³ª‡¥d!¤Ë ;æR„RKj“æ0ÀþžH@¼êsЦBp¦åÂä]PJÄ÷fò¹‡£l@àö8ì.L0Ïz>[¤°wIÒ´”¤„N¯jIlrÕ—B°]ºÀ»\Å­0°&€(JêJäz©áàb¡ÈÀ80¾PZÌØ€ÆR‘¨K‰ M܆A!¿CXÌqßøa”àx#>×fy‡ܵN&Â~P÷‘ y$_àÊd6 Ÿ<£æb‘Ô—#3ž‹•²Ûe8XkÈø#ÝÄ–Üæ:Ç<†:B^s!:ÛùÏ+›54æþ ™¢ÍèF;úÑŽ´¤'MéJ[úҘδ¦7ÍéþN{úÓ µ¨GMêR›úÔ¨Nµª=ý ä0ó#”Ÿl‚)#°†¸Îµ®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Ík„Ff¬*_3øbáBXµ¸ÇMîG# ÜèN·ªÏ­îv»Ôì~·¼ç]i®Ôûa@¢?hÛ‘o50Þ@uo€€ÛØà²4 †?ãàp††fL7<ÝhIe†x6þ Ž;ÃãÌÐ8úЛ>ð¥zêOÏúÎ/Ãõ¬':ìW¾ú؇~öÈÀ½íë¬ûš×~÷–ï}1„|Ç/¾C“ æ+ÿ„ÎÿEôŸ¯¯é÷ÂúÔWöw±ýìã¤û¹¿÷k"þ[”ü29-Ôþ—°ïo?Kâ úË?%öEþïo’ý·Âÿü7¸ € Q€©€€Ø xþ ¸€ñ€¥ x8 X‘¡Àø !ø1‚`‚$¸(¸ +˜‚Ñ‚™ƒ.X2x 58ƒqƒ• ƒ8ø<8 ?؃ü„‘@„B˜FøIx„ö°„à„L8P¸S…#÷{M‡…VØeU˜]¸…¤qa†O÷…‡`†dxtZ8uk˜†Ë׆Y‡n}røuu8‡Õw‡e§‡x¨}|¸v؇߈qGˆ‚H~†xw‰xˆé·ˆ}爌è~8x“‰óW‰ˆ'†–LhX¸‰Ûð‰ƒ Š ¨ˆš¨z§XŠy˜ŠjÈŠªè‡®˜…±øŠƒ8‹lþh‹´ˆˆ¸‡»˜‹Ø‹vŒ¾(‰Â¸‡Å8Œ—xŒ€¨ŒÈˆ˜èxÏØŒ “ÇŒÒØÔ˜yÙxAŠàܨ àøãŽÇPŽ}€ŽæH ê¸í¸ŽÍ·žgðøï˜÷X×'¯ÇúX‚þH{ôø‘w`‰ Y ™ë¹‘y 99‘õ'‘¾7YÙ‘ÿ§‘ÃG< ¶Q"É!ù—l™’ø°’nppP6y“8™“:Y@’0Y2ÙGP”Fy”H™”P>ù“ã”pD©”T™”Lù’NIP¹CY•^i”þW™•±•]'•_ù•a)– A–iЕgY•i©–*Ø”Âà–o©”q)— Á–h`—w‰”y©—I—ñ8• ˜„)˜ÙÀ—KP0-•tkc¡B~y˜E˜ŠIŒ©"@-{ä>þ‰ÚS™–‰™™™ƒ‰i $ð™ïÄŠ¦y˜¨™š±™LК²Wµ›9›´é¶¹­™m ÉÐ%2fi™G雿9„«Y ¸™ ð0ÉÉ›wéœÏ©Á©ÓÙQõIÜù–Þù1¤0žM $ûµœÌy™ì©žÍžIðeÄ©ðIG   `˜ÝYŸöi ÍÄþ >ØàžKPžñI Ì™žª•ú ÀbÑÀÚÁ.À±À€Ú¡ó –Z¡ç¸¢C§ÓOÀ±-WѺdpò9ŸÊ¢ñ€Ÿapžg¹£<ú> @Š–.:¤u™¤¶p¤^)¤J:†W(¡§É¤Qê EúN —Vz¥¼¥i—£Ú¥^ª `Ú[J•PZ¦çp¦\¦xI¦lj~rš‘T*›u:§´à¦[§V™§z* |ÊxbZ¥X¨x¨Óৈ©¨ˆº¨€ê ŒÚœ‘ú¨#é¨Ðx§½Y©–J€œê©šZ ˜Ú©Ú8ª“ªèù©¤ê€ªš€…Ч¦ºªâþت¨0©*«²‘¸êy¨¤´š«ø«¦`«ô¹«ÀÊŽÂ:¯º©Æz¬KÚ¬´×«H ­Î*}ÉJ ĺ”×Z­1¸­¢­kÊ­Õ0¨Y®Þ*®;x® `®ÔŠ®fª®"¸¬¢ê®O ¯žÀ®ôÊ‹=*¯©Ú®ùÚ¤öz‚üê«þú¯{°œ€¯kŒû*­Oа ‹äúv;­Û { {±…X°Èê°\ê±{© ›¢Å:²[²&®(Û¢"»¤ «¦Û²ß8³Ý³qú²4˪: »³Ú³Öг*´@û­6‹ ?{´Ð±V°´L[ª*›¢,µÖj´ûH´jµþЈµ_Z±ëµ\« N[P;¶9µ:š´hKeKgÛ¶©¶c*¶r›®v~`²wÛy«{+³Û·ð¶—¸9K¸i;¥&«­ƒ«¸Oȶ–·{µtk¨•;·Œ»²’›¹mÙ¹”@¹žË}  „ˆ[´£‹|¥+ ¢›º€û¸©µ” »®K†[w§»µµk¹›Kµ«»»Zú»…›»³ ¼X*¼JH¼·j¼ûH»ð§¼'˼¤ë¼‚ ½Ž+½Ó{¹°Š½ïJ½vÚ¸U˽Ê꽰кâK²½»¶£Z¢†’ç{uÈëæû´ÊF¾µ{»Q0¿f+•;Ù¿;Ù“öëºø ú þ·Pà¿ô:ÀOPÀ‡‹ÀœÀ©ËÀNàÀ¸ Á¾+Á£KÁM`Áù{À ¬ÁžËÁ¢'»Ëû£ Áï‹$¼LÀ)œÁ+| Ì/ÜÀ1¬¾3u5L…Ö¾œÃu»Ã ÙÊpÃ,ĘKÄiÄ^øÃÑxp¬ÃL,-¬HÄS<ÄU ’N,±P<ªRÂ]ìÅ"¬ Y,z[¼Äe<“_|†a,c¬ÂmìÆgìª&½FªÄÛ[ÇBùƆÆ.ÌÇÌêÇ|ǵÇ(¼Æ}lÈe‰È¬šÇ×»ÈdìȬ½…LÉtlÉ} Èž¨È{ÌÈ™ÌÉgpÅI ÈXLÈóJÊ¥ìɶþ ÊÁ+Ê«ÌÊeð‘%  °~Á7*ǰ¦²Ü¯´lIÎáãr6v¿Œ¦ª,ÌÃÌÌ\r>ܦ´ÆÌ’ Äj\ÉÑìËÓL€3ÜæÐÉÛÅÏL°ß<%RÜÖçÜÊé,Æël±íŒÂá<`)ÜvÙìÎÍü¦ù¶ûÌÏÀpÐò ƒFÐ÷ìËÁÌÎ ¼ý,2+Msû[Ñš,æ=à Ð>çË"ÊÞ\Ò}êÊ£XÐ3}Ñú,Ó3}ÒÁúÒ±Ó:M±<ý‚!ò±úµÇ> Ì@Ôô;Ônð Õ $ÀEþÝÔÓíÔåJÓW@àgFà˜™›‡¦\.†Ê§|Ð|ëÕB} 0ÐG ÐGÿpÀ(¡ÄÖ”áÖ‚ ×qm æüÙBÖm(ºÉ?ÍØ‚}¸P=%Ðt\®9Q6¶V46M¨\ýÖmÀ‘Í € pÚbONpØH0…æ/~}sLÒ¡ ÙÀ`ÚÛw]pâyÙ±$g}æŸýÕŸصmÛ×à ‹Ägª­œKíÌŸǻ£=Ê})¯-ÜϽÕZ=݃\ÝgÀoâýÀoåÍÛÈû‰Ù´¶ÙÊ  J´Ü<ÈѺ޽ º `ݼfؾmbqþ\üâmÚÝÝžmà÷-ÛûÍÐàäýàåÝàM¡Ú¡Ú/c{÷Û¨Wߺ›àA Þ£_ì"AÉ•#k=Ü âÅ+â#ŽÉ³ÜØ´-ã©Lâi@ðãSÎþâCŽã8ªãÆ¡ ŒÑ<$äŽ ÝEŽãéדA%É™ÕOÎÝ[näÂäfP»p¢áä7ÎÔ]îåÊ æeÀáU´#…mÏQ¾¿0~Âj®àlN°°3À<µìâ ØÒ}稗çcà&ppœfNÅ0æj~‘ °%0.ÏBeaºÝ7=ç"~‘U¯¦ågå”îåþ ¥èž~à©nä™™åÒë þÀ»~ß!XÆÐË(ëÄÝëÞ-“ØÃ( eê’nãÏnèœèg ‘ÎÅ“~êÒ’W>áRfÅŽì0\çz,íÓ €/­î¾JMî8lîéþ ³Y.ç³N¿ò>ßp}‘Í’‚Îïð^Û¦üׯNäû.ã¯à ?èý¾à–Úð¨÷ð¿ð¡.ñ~àáññÔ±¿ØÜþéŸà!_à%ÁÚ'ÏÙOç+ÿØ-ïÜ1Oß5ÿó)Ÿì9¿t;oÀOï_NãмíÑŽî3£//ô7ïÕKÏñÂ=ôþD¿æFÑH¯íDõAÏë=?Ý\ßô^?òwöOÿ×T_õfÿõñ~öA½ödìloð?ïŽ]¯òsÏòu¯RÏÙi¿õ{wÿÁOïpŸô\÷ªø,<øåîö:}øZæŠOëŒOÃb÷•/å—ÏÙOø/Ó’ÏÆÐ>ùJßùEüùŸ÷2únëømÏú‚=ú\ú¤oø®ßĪû›Ïð¹oŰŸÄ…ú_„Á¯Å¡_Ò´?ʶ_û¸_üûûŸü½ü5NùˆoèÖôÍÏüÏõ9Ýý×OüàßÕYû䟾¦ŸøÙ_ö¿ïÅÒü²óП¼ñßÍóõï¿÷oþóùïÔ@€LEã™T.™Mç•N©Õ¤bÑn¹]¯¥2´J…•ïù&ŽÙm÷—ÏéuûŸ×ïùSáº/Pp ípK-¯ Q‘2Rr’²Òò3óéO³Ó“ «íñŽqô¬ôs•µÕõ6“S¶–Ò5M ï4·KÕ6Xx˜¸Ø’öXy×— ˜®×Y zÙú;[»5yÛÛ©yl×ÀLœšü{½Ýý=ª~=|ºZNÚ^}ž¿ßÿß–<€×ê9»'ŸÁ}6tøP@ˆÄ ú:'¡Å…9vôøñˆD±*æºø&£É#Y¶tÉNäËU%Qt“²æJþ™;yö”Óç¬PèÒ²ƒs”Í K™6Ô©$šIub4GtœÑ¨[¹veVÕëÓ¡D•²AÚ¨lXµkÕBeq,º´VÎ:ûo^¦nõÖ™Šön›º‡æö5|˜#_ÄVEÉ lö*ÙÇ‹)Wö§Øò˜¿vµF‹ì¸sfÑ£áa& ª±¸Â…>«ž|vla¦e7ÙLøu•Á¤r×öý»màHn󯵾ãÙ7ºÜ9“â©zûI®ú› ¸w÷þü÷ÑÉ“î|º®ì7¯k\/@|ùóé×§ÿ¾|þµç›§÷²Úºs@[$” <Á¬€‚êô{p/™óï ¡Ø:þü4kO% !ü0BÉ£ð 7á0'YÐ5A|Q&þ&Œ«EYTÎF¬„‘Ç‘dŽÄDLEª\åFìz\2ª ²¨%;2ž"£’É,rò7(³’r@^¬äLË2yâÒ7/w¬2I÷À¬ÑÌ8[B³65‡´mLܰ<±Íåü$:e³sO"ûLñM]t"Ac#4Q%Å<ÔHF-uÈQØ TÇ;¥ËÓ¸KE(ÓÓ6-‡Ò+#usÔVç)Õ<EµÓBñL•LWumÖÑN= Ô Wõs×b½éU´_ý V½a5ZlÍLYÏpÕÓÙJ£ÝV™i-«ö«kCåtþJnÍ-ÆÛÊÀEN\aÉ óÜxkI—²ubö?O—À°Yyý……ÞÅ쵪Ý~i-÷ß„Y ±Q·B[?-8_‰¾Ø3‹£*0€ƒ%6¨âd•ô`x¥8bŒ]~.¶ÐàƒØ@ Є¸/¤“YMN¢g}éHn©ƒ›X`€œ«r˜=–KÔø ˆ¯NškdôuêŽà€’:ªá³ZH¬Mf;Ê®åîci–8àˆÈ @€BÈ:µ£—…ûËw‹ž›ñrÜ^ªŽ ’ð@‚>È`€ºp”W®5[U?}Ùǃ2@r#(·þ¼ hÀäЇa£EGwöT÷ ¼Ð[ƒ'tÒ7ÜZå{²ß{z €#Ð6y#„ î°Ûåwo>wèÍwbÔg¯›j:ä" ~BvÚ“/ÿpçqWùüþ‰”¾'2Ó€¨‡3ØlòÃ<€Î}N{B#–în—?üùƒY O8æ1Eˆ€€8@„ÓŸùG¾f†&¨[_ÔÄ­éBZk[ yÁ ÖK‚Ï¢à÷·¸fp†z©á†nè¾·­ðˆþKb^–HÆmˆBŒ"§ˆ—*êæŠ‰Ë¢¶¶ÈE'Æ |Zd¡]ÈÆ2Bþ¯‹où⊠®:¾±wqdËtÇ{5ñ‡xÔ•÷D2‘yv¼  yGȶÒtcŒ$"ÇÇHÔ92,|D’ ÆIKÊ “^Ñ$›,ÕÇÂô'ÜìãU¯²Õv~usOLY{¢ÖR¾52sé;¯™ÂÚ¶&–­jMÛ˜Ó×õ4 ]\^ý:8Ø~ã™;’f6»4ÖÛh¶PÂ}Dn'¦Ø°þvWµýq_âÝæv4ÓÍkI#»×în7¹×ý¼s7ªÞø¾73“xWâà<ü7D¼]ð’æ[ÖåFø¾£¸ð‡4âÕìw0>‰ŽëuÞÚŒ6‡§íç’wšWÅåýì8o¼²0¯­ÌÂò‰K\á!WgÀ žqwÒœ$6¿…ÐchqLñœ‰@Wy·ˆ.•¦oÖå|VþúÓ•ép‚PKßr’§ŽóMZÝZ„ØÏgô-!]ã`o%Ðíu§»ê\W{Õ}NǹƒìÈ;uR´ÿüîË­»´°>ö—½ïCíúâÏBºÐˆ>¹´]±w'~«·ûà¿Îù*Uôé=¼ÞGÏ÷¨7z䟥s¨½køP Ô¥>u„‡LëÒ_þô¬N½jW¿äjï¸õÖ¶÷±3aù೟õÛ?÷¸Þ½rGê|vOšæG+Û'Oò #Ÿ¯Êïüö7Oÿå¿ùÝ7<úÍ ÿÉÊŸ”Ôϯô¯üðù.øþñÂØÆºð ðü Ïþ<îûd‰éÍE'gîûëçË÷2Mî$pýBðÀ(ÿ°¯ߎýx)EŽ]¯ç^píTÐø0»ÆìÂúZûpPû„°þŒpè:÷NÓ° / p IO Á ‘pþ´0Ÿ0ætpiË`ãñ œ$¯òüµ6?í÷jOÝÀð yPé ô*mñ$ýŽ ÅPЂøâÐ ApýÒL¥@bO f/èöPñä°…Oà¸0 qÃT¯NÂÐq²Ðó¯ =Ðw?‘D׿@1óúðyÔJápø:þÞ4‘ÞPQLT1ùXñÏL®÷ÜP×$ñ)Ñkp‘ØŒXt1þx±ý0ñ ÇPI ‰ÑûQ›¬1c8Q=‘¥°©ð#PéÐo±‘Cÿ‘Ú‚1gq]0鯝ñï%×0ßqiqç1˱]qPê1«+qG §Ñð!“ð“¿"g+!‹0"·p#»p!E°"Ÿñ1$ñQùq;R!ò CÑ _5å%Ññ$e±ãñUR#q"yR"ëÐò ³$uÒ ?r!Q(a²e²$­â"Ùc:烖€Â&ÀØmþ™Ò½åÀ’òÄÒý¸a&3ï)&*×A€jÆi’  `6À ¶²vöÑ&SÒ'9²/=’%Û°)Ir"Õò4šÆ€L`~’ v!.@…”Ò%*1Ýq/¹Ñ(ч¿r*ÓöÒÒwNƒzƦl’` à0è~&S/©uDòá%Ù 6-Ûá6?³0C“42‡gg’ pŽ ið2‚ºR6Y/f7›33Á1:™à9ió2/¡ Í0<­:™í,¡ò4"gr*' Žà ~’ó5GÒ:as3Ãr9aº“/k³ïÒŒÂ>ùm)i’4XgrvŽþÎÓÒs=EP9qÓ=o’ˆúS§‘Ò$4'ÿrå`OŠ/4 A>ÖÒî&o† À~Šà8‘ =€bTFg”Fk”&àf¼Ã6€G{ÔGH{Ô¼cd&ÀFÔFqtt4H›ÔI‡´;ŠI§TF•Ô;ÀI³ôG¡”;¤”J§ÔJ»cGµ”L¹¼ôK4L¹cLÉ4KÍÀMµÔLÑ4MktMA Mç4HátOùtK‰ŒôN“4G»KµO¿ãO•GëtP ÕP—TLHýÔR5JK“4 ΉlÌ N35W FF?SUUW•U[ÕU_þVcUVg•VkÕVoWsUWw•W{ÕW_Õ3-~ž&jÓ1 8€6Àg´ÓYŸZ£UZ§•Z«ÕZ¯[³U[·•[»Õ[¿\ÃU\ÇZ?`Jæ4 Èf ˆzX3.ç².@T%Ê^ïu ¦2`=ÀT‹à*=F+ñ•` Ö`aVaöôµ*G#=-2J d$€8ÔuvRÈ0(Öb1Öüu>PÓ0<ÀHhÀ(@@c€c?¤-Û•4< ršõа/D@Àf.àdG+9@.ÑU/t–gÀgA(z†; H/2à8àî²B +þ=@h€hõcXågp1hvAGƒö@€f ãl“¶ z*£kK`gçvm‹@vÞBFÓȆD³AAc#n ÌóH17dëÖÄÖ0H5qT~HöALT8‹G4hÖc`êÕ2×D`oŽ >µqÑöqÿf  k·8M—oŒ u%—9ÔuÈ3Y"@s4À9í–uM p‹ T“5 ƒtM 0À0çcp÷-`iW=KuyóÃ`< ô4J`„gt‹÷x‹@yWd•€lj1$àt±wAU3vÉ#xN”scÃf¨/H—vQWu£þyËÓãx×ôÆ}MàvÿrM TkÃSù÷-H·rs™·xárvã€å¶÷i €‚}ãk/´X w2˜2šWmÙÖmávƒ‘ }ã€××^˜o‡õãxØ5{GÃ$€>x…WX9@g€8`¦l-ªp`p­¡êƒ5Y¶„\1ê˜>F€  @Jx+À>´§e_–aÙ‘’#Y’'™’+Ù’/“3Y“7™“;Ù“?”CY”G™”KÙ”-Eh™þ€L7X•à:à”g¹/ζÈxq;êW àh˜ßâ|—À§ ˜#w  €ƒ™™»Bdãcà×R¶ªÖ*óªv$@g–— „è6Ô2À²Ù„Ö×t1à¹L—sá7¬w]éÃ{›™Ÿ—Bt@ Ø `…é²8kA¦ào(yज?‡n z·Rf <”o`€D€r è€ àÈ—û+TV ûY¦}‚l‹@š‹ sàU~Ù¬—hƒ7(Ço#÷„æö-‹ }d¹¨'…L7ƒ]Ù¦ØS ¥þgÚªy¢¦M€ ‹ ’Õ uŠÓà7tf ÆCmÑÀ=Æt‡ºtß~_Vgеª¯¯[âxoÚÄÓ`ŸïÚyMõ€ù€X1ŸútÛùgQ~‰¶›C˜§óš²? ~ø:e‚Snó€ËZ=Ácn›­€fqæu¥²ÿU°+[¶"«5û6»|yI([ è ’„LÕ·Czž]ÛF†5`™gÛ¹â0»¶º¯o;8šºŽYS5!€8 ’pŸF8Ú£Aºt t< Hàµ9 bú¹ë»j›•¹ú¶÷ÙxÁYŠ"@5Ï©4VEñr¼‘:µ™8®[ûµY3ÀøÛ¾+ü%· 0ÜÂ71 €.9 ¡ó— ЙÃKü0" ÷õ•£@ÃMÜÅ_Æc\ÆgœÆkÜÆoÇs\ÇwœÇ{ÜÇȃ\ȇœÈ‹ÜÈÉ“\É—œÉ›ÜÉŸÊ£\ʧœÊ«Üʯ\^‚!ùd, X… 1"""+++444<<<Mci|CCCLLLTTT]]]bbblllttt}}}ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@”pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›´Hˆ D'þÏž ŧ%£i ‚ÔMSœ'4@€ &–x€ @P%ØiS¤%vJyŠF„U L(ÑDì‘H$a[f@¦jϰå ub(H8@€Þ$â~EB"€e²€N ‹¢©æ¬CÑÌ9ÍL˜À8€&và-m„𿀚I«hïÂO¡0 '@7Ò@.`„“ᄺâFñà5Ä’ØE  ]Ä:ܾјOœ£š„XND€žwñ€ê€  ±AdHpz((GA,ÅÞY“ù4‚–!>‘ð€þ„Д‚áý囆6  PuÀ…”¡¦]`ÀwÜ5Þ^;ð@…,ÑAW tC‘‡|#"ÈþWšN”°a¼FÄ€x ƒÄ‰%f¡E@"–J¶WQ~4¡QEŒÀ(] ( fFp€<À¥I0¨p@ƒC(—Aˆ¹±W&P Ðx¥eh]0A"|À£D ˜ hc#(*¨‘7qp2@G v„x²qjª˜zE¦§84@dZ•"ðªe=j¬¢¦Š†BŒ*(¡B0›êxí¡‰.*bþ†Ahç­³áÞª&DP0è&}(œp€ÂÙ©èÙ²Gèd@iÊ= Ä|€v¾&8*ƒ"¢`„?éB¼'€t/©Ž2X±Çm a_ø˜/ Œ…¬-#!»ìRÀ®W½õñ"T%\]è9{L* À÷0 11Õ'w$Ã8ÚÅ(KÂ|HŒoÆ•ÕúõÓÊÅ7¯Cx‰e¡ó©]'"‘ßI|€h |Äii!¨p0ìÔÏ÷´NÖÔÆ‰Bâwê4€åM9)PûŠšå4€²¸qZÌIˆéº˜ÚþÈDÀJ&æDŒ€5PC.€‰/>öî“ aháBP(\Z½£0Àï—+NÄ¢ÿ¦SóÏ¿ ‘  +|oñÈw󄜀¾åE0FÁû=ã›÷±qñ)QÚ’‹fV²EðÈÒⶬåoÕòšÕpÍè¨CÂi~³÷q<ú»QuH?ÅŒ1A HÖµ"àKÈ¢Íýø§|Mà}ð“_h(„¢€1äéRÖ€BCt…}Ú[H ª"ŸåœÀ¡Ó›~˜à¤O Èz˜¼#ö1jjs^Æ(§›ÿ©M9£OçÀ†@þ©ƒCP Í–hœ áuÞAþEÂÚ”`DAM˜I ¡f(¡Ê²&ÔHÑ;Tâc Ù'-6 âCpÈ„ð á~†Ì¤Xȇ„À@$cB)Úxó1ÙàÈâO'¦ëàÐ\)©T•;”c,ˈâôP„©+Â!€:>Bðà ‹i+þ7ìã%u(©Q&a=>¬Q4I¼ÁXˇ$D8DÊé1èL'cMáë(“p¼$ÏœÓ]o®w2Žuktµ“-5&ÝÕŠŸÿü 2õØ<ÀàE[’v¥Á#¤…vC°]ôøš-~Ó†÷õ$UÏ=S›!eœèÔþImг ÈIS#Ôm XÁ„°AN€h¿âˆ´ªõ‹1;AU8s‚¥U,-Dñç×ÈW´8ó˜º9‡$¶´Va¡2q‚ºîLT6ÒLØ&‰f™£QMb‚jÚGµI6‰!5k@­ã âØÒ©•Íjg»äBÙÍ—D¥‹À§Ä3€Îf;è¬Y ¨^²ñ!8mzJÊxE5p€U† -DÀ\DÈO$PDî'æù_³À¥©\>Eµˆ@k¯8P `I8 |VÓwêÌ¥”,CxmªX¦M‘új ‚SÃìeÏÙÕ±vV»]mGGÜßNw>ÌÝw¯»Þíwrô}ÃßÅ1øÀ^ …GâÏx2,Þo¼ä½ynT~ò˜ÇÂåµ±ùÌ{^ ÇFè?Oz&ŒÞ§/½êzj´~õ°½4dûÒÓÞé5¯½î5Ÿ{­÷~÷ÀýïÇ>üàßôÅg{òÏ|Ö/_îÏo¾ôoÿ êKðÖÇzô¯üì3ÃûÜ;ø•1þð¯ºüÈ@¿ù3­~c´ýj~?1äÃÒ_÷¯¿$ó þëÿmþç øí1€¼`€¨ ˜þ€6Ñ€¸8h 80q´ Ø( Ø*‚°@‚"x&è )x‚$±‚¬à‚,0¨ 3ƒQƒ¨€ƒ6¸:h =¸ƒñƒ¤ „@XD( GX„‘„ À„JøNè Qø„ 1…œ`…T˜X¨ [˜…Ñ…˜†^8bh e8†q†” †hØl( o؆ú‡@‡rxvèyx‡ô°‡Œà‡|€¨ƒˆ¾çsíPˆ†H|ˆÈˆ‹¨wŠx‘øˆÐ׈ë0‰”ˆwÛW˜˜‰~·‰Ÿh‰ž˜g8¥8ŠŠŠãpЍyªHx¯ØŠjŠ€@‹²þÈy±˜Š¢x‹âd‹~à‹¼ˆz¹ø ÀŒ®7Œ®¸‹Æ€ÈØ Å¸ŒÑðŒz Ðè Ôˆ×Xa׌–ÇÚèÙÈwÞø,Žu`ŽäX è8똎ø7Žjòx í8¿p‚Wø8úøÿØ ÈÕyŒÊx1alà ¹ És‘‘ˆW‘„‰{ ¹‘娑Õ'’ 9™'Y’/H’Ú÷‘*‰)y1ù’9È’ßg“4Y…8I~;™“ZØ“é”>ù…Bé~E9”dx”ó§”H™†LùŽ.Ù” 1“e@•Rù V9Yy•Wø”Á°•þ\É…^Ùc–xX–ˆ–fÙ‡jy€m¹–‚ø–•pÉ‘t ‹wY—F(—¹–z9 ~ùù—uÈ—œpR‘_Wåæ:Ü6˜„©‡†¹ $*‰ómCà' ¶&왑ù‡“Ù ÷ƒ™Éô| š„8šœ`dda¡Œ©©šˆ0›®¹˜²„ðBEsyI› a›¯Y @@½9›ÀYÂišµÁhâÚÍIŒÔ© ® F ZÓù›Õù“ÞY ‹†›IÀDðàêéh÷!Oë)C×NP=ÄœîY‹×y à~òàˆ£A   þœÓùpÌvfùÉ–á) …ä:t+œq,–QhÆ„ïqoú é€ŸWðp&z¢(š¢*z°Ÿ Ú "jïaP£6z£8š£p.ú¢»£U0£::¤:Ê£꣜أ¯ ¤DÚ¤5j¤H:‡Jª‚@£NÚ¤P¥ø¤TÀ¤W:¤Yª¥öÀ¥Sà¥_š£a*¦—Vz¦h:¥j ‚pº’mê¦7š¦q—GŠ‹uj§O:§yJ¥{}ê§x¨‡È¦~Š£‡Š¨ŽuUº¨w ¨ŽJƒ”š fº¨Z©!z©5Y¨vº©œzd*| ê¦¢:ªI:¨¢©’ú§¬ªªþ«Âxªgšª²Š—Šúª;꩹ ¥™j¨¾ú«XY¬¥0¬¡Š¬ÆÚ•´Z®úª¸Ú¬ÎȬ£ ¬¨j­Ô†Ú ¬Ñ*©Óº­¸ø¬Çh«_®âÚªäJ‘æz¥èš®Â¸®³÷­šÚ­ð ‡öz¬íê¤ïz¯³º«Òš¯þ*™òôJ¬;°ù(°€­·Ê° »š ;’ûŠ¥±µy±bY±DÚ¯Û’ ®û±„¬Pà°ç:²$ w*{ (ë®-»²`²Oð²ü³2Û4ë6k±›³µ°³Mг‹³@;F ˜»¬?{´rÚ´7ɱ`š´N+ŽP»R[¤T[µþt ´È—µozµ\»’b› D;µe;¶˜ºµ… ¶ŒÊ¶j p+™n;©i·>8·p¶Z{·x;„z+šuk£û·0¸‹À·ak¸ ©ƒ «Œ{“~«ŽK›­“¹Rˆ¸«ù¸½z¹˜ë¬![¯žû¹b9º_Y¹kº¤ë²š›Šû¶ª»º•àµKðºv+»F»iɹ…‹»³@»*†º)«»¾[˜Ä떼ۺūÀ›¶K¸Ê»¼¼w¼?*¼0K½Ò+±¡‹°ÙK–ØÛ—Ö{³ßÛ½†Ð¼Hð¼K¾‡;¾·€¾«¾ë»½L ¿nɾ¾>K¿Õk¿A‹¿EË¿ú«³Ñ[¾þ‹þ¶ ¾ü»Ü·Œ ,§ÉûÀ |æ{îÛ»|­ìœ ¼¸,‘\‚ » Âòk¹%¬À!¼¤#|») ì|üÂ0¼Á%ÛÂлÂ4Ü1<3¼Ã‚z© ÄA츼ú¾DL¶B<¼I¬ÄFÌ«ÜĬ«Ã­pÁ6 Ä=|ž8œ¾R\“TL§GÅ]¼†W̲?<ÆyûÅ«`ÅjŒÆ`ÅD³ÅHìÆ€ÛÆk{ÆtŒ„elœÇu¼Ä×ëÇlÇŸÆyÏæ ‚<½€,¾bСöFȤ Ç4‡Ç”W¥+šÉ+Ú¢’ü¹÷xð  °¡,ÇbŒ|`ÈŒ¹íþhÈ’_y¬c}ï±ÊP¼ÇœÚŽ)’7pÆd˨ܑ¹ÌÊ‹¬•¼ìp'pá$“Åœ°Ç¼Ë­¹í˜eýgÍÄlÉûº°×̸í¨.o”70ÌU9ͧ<Î"[Άێp?# ðj§ Î\PÍäœÌ§,Ïpp5“_ùvÌëθ,ÎÈ,Ðo¼ÌOÐÐО íÏ[Ðñ ÑMÐMÈРϢëÑ‚)Ñ¬Ñ MÒÜkÒá Ònà~±?3Ý"}Ò,=¿.ýÏ(½? ¢5Y6­ÒYÀÑ%½ÓÝÓP`YˆY)ÃYDgc>üÐþ#ÕJÍÈÆÐìL¥c(йe]¶#©sËYmÍ[½Ð0MÐü·™1¢pÀpqlÔ¼—Ó(¼Ö\M 'ð_­ôI3ìügZŒ×#êÐjÍ׉ÝÖlÐÙ pù…ÑT ä)P+vØZ}Éz=ÄŒ-£L‘ Ùé)3·ƒ}§fNƒÖ8ýÙ£-¬¥ ­mž&RàN² Ú´]Û5{Û_ÛE°›DÐÙwÜKÚL,ÜeJÜ"¥rÔ Õ½ÚI0žxRØãöà`›Êµ«Ø ÝMŸê)Ÿ» np=ÕtmוÌÜ+mßèí¼Ò2 `ÝþÝß!`þÝMПÿ ÊT/WÖrÕ‹ÖçßÃíØ0¡bB‰SU½"2ŒßyÍáÞœ¿ íÜüá<»ßT½¹…N@]ÔNÚ$îÈ&>´(î+2(bÑH.Þà³Íã3®ß®>xÊЛʌØ0þâ3ÞŽð5Gn–äLÞ¥æÝÑ?^ÞAž .ko-ÍJ¤WžÔYäÀ0¯Q-ÃÐóÍUÝ1.âe~¾5.æ… È æo.|qþ¿snæùøM% h—æVè<è\ç( T'ÀmíÌèpþçùÝŽ ´–îç>ÎŽÎÀî|[ŽÐþ€< þà=îê¥î›Â0„6(`Ê­é';æ-ëq|êkp1ç;뢮ëÐm$0ùUìXîàÏîë*éV³ø’-‘јèÈ.ÜíÈ^^q Èá¼í¶=ê,íÓìfЀä­íÀê¥ÞŽ÷,·æ¾ëèNÂê¾î!.è#ÞíµMÉÓžïýï.¬îÿÛÇNï¾ð7=ïÆ^ïÔ¾âŸÃýîïO<ñÍ-ð£ ñOãÏÅÒ.ò _Þ%?Ç'_ñøžòÁ»òäÍ×(ïð÷mós^óó;Ÿó.ï ÏÓ2ÿóÕ©óòÞð=_æFOæ¯~ôÏîfñHïôþOô)õBók½ô½ÞôLïë\¯ÓÐþõ±ö{=ö]öV¿Aÿñ8¯ôkŸ‘#¯ÊZ¿Õf/Úh/ö-õ³‹õnŸôY~÷Ï÷g¿÷ÿþè„÷†ßñTÏóïó|OÆs¯òu¯Ô‚_â‰?ø‹ÿmïødÿô‡Oê™ù›ï{“ó•¿Ó—/ã£Ïú¥Ÿˆ~ïùi_öq’±Ô¼®÷jùJ{úú=ô¼±«/ç^?û ÏøŸÿ÷÷Á ûÀ¿ñÃð­Oü»úé^üºOûÍß¶©?ÑßïÓøÚ_ø×üƯüÉ?èã/ú坸çÏù·ßáoøµïìëýßoÒíŸþý¿ü@€‰Eã™T.™Mç•N©Uë›Õn¹]ï÷*ˆÀeó}[0m÷Ç\ÆfñEž—ÓÉiÿ0Pp°ÐðqJ¬/±Ñ1kMOÒÏoR²ò‘³Óó4Tt²Žô”32Soìn•Õu–¶Öö·v1—M6®õë8X¶9Yy™¹Yh×9:ê·˜òØ‹¸ÚšQºÛû<\ Z¼œZ[ûRû-½ü>^^™|^ú¼Ú+›]ßþ`@ƒê L†¯˜¿qëØµQhbD‰—¤h !°‡Xø¡»vdH‘ÿ,Ž$•ÖÆ0 ª4ùfÌ[%ezB¹Êe•þŽù>Öôùh'šAÝÌ”“Ê΄=‰6uú´ÌP¨°i8‡éBL-³NõúlÅ®aÓ„TË~cɶuëTêÛ¨U¯bå¦nëÚ»rùöõ×ï>ºWÑJQª‘m`Å‹f¼Ò*×½‚ózœüsæxŽ5,ÙRež—;—6ÝŒóé&f5%Ö©Ö²jÙ³º¦„u,Ò¥D/Ý}xðG©…ɧð4Ø£‹7wÞˆ¸óã{l§íø÷síÛÕeç>ݘ÷××SV—rBDzõëÙ·g‚{ü¾Ñ›ƒ‡“Êáòâ“ðÿÀ ?ù ü‰¾âìkÇ<åÈéÁüªº€Â -¼Ãþ ,ˆðÀGJP¸· ­.üžÐÂ=\Q$ƒÑ!@ñ(W[Î7u$ÊEà`´‹DÂld‚Ƴ†ÜI{z¼íGgÄ» AK’Ê—–¤­É#•(²5,QË*ÅüæÊÙ²ôRÂkDóD(÷NŠÊ”íL)õ²3¶8õ4hNÕêœKM#Ù|2Ð.÷<4 >OûÓ7SÄ“9D%•GQÓÆÑ5!ÍqÒNÍ ÓÏÏîÌAo,T7OU§ÒÒ.Å«ÔM£\•ÖhZíìÕ.¸L•T!M­X›@]TÔ<E¹aØ5Ù_ƒ}:e]-6ÒccíuJhµÅHZ\©åÔZ_e}sþÛrg¹U³\)»6ÜlÍ}7t:A‚0À$:Є"ÔÝ‚YêœÝ2SAá=y}j@€ Bˆ€ŽàD!=øŒûvÖvGõØX„EŽvà§>ˆx˜¸«µbSd™æ¢ºý)ˆØ7#öÝÍeÞ`†UÜš&Da™8€ˆØÀŠ@€FX–crA®6æ¡þÚ—›}*À"J@#@ àƒ6°÷ê#û¸ë¢·ì¼-)Ù)Êâì´• !€üÍúQlé&Ú]½×Uìš`zˆ€z t–Ûëuí®»ñÇEç(r™"`g¶4?œ€\Oþ`îÄïîôÅG¿ý‰ØÝJéä|…h`å%7œóÏ·ÝóÐq¾M¾bØáÓ%Fâ†×À4€ûðع^>dòLJ}"é„ ¸Wx @{&0 €€€¬ÅÇÛ|„œÌ“>ÞHz–Bœ¦W¾vî€|Æú4Òð>‘ X%ØÁ%pZÿ£]Gø@åy0‚IcŒW‚Apc. =¨ÂŰРÌ ‡ ÃÒ°† ÇÁ—ˆè³ah°ž¯yÌKâó–8Ä&ꉌ¢§HE!ú…ˆý‘aŒx8Á1©‹¬"¯È«Ùiþ­v\LãèÖ8Ÿ66k\²‹#ç(º:ò%ŒÖ1¢ÐØÇÇýQ.ôÌtªAlˆ|‹"¿TÈ#:’z“¤[(™¦G¶ð“™¬Ù&ÛÒÉ61Rƒâ(ýøE;ŠŽ$Œ¥ 1ÉÊ£‘’,¦$T(‹hI[ŽÒ•€¼ã̲XÂ-òñ—À,#ftéH_^—É|.ÃÒLõ¡r™>œ¡4#ÌD“ƒoÔ£,ÇÉMe&ÉšbÁfhÎ[zs’àÜ!;(ÎvºóœHJ'ÁÖYÌYâaÔË>q£J7îQ‹…—@¿BP$h“Œô\¥BEÆP¯8T Å£?ËIÑi““òü!GïIÎ’zôþ£Ë| F±ÖO{Ö¥ i)EºM„`%fLeªÒÖ4¢$…©I‡ÊÓmYt*,=œKoúO£> ©PQj £)Èg>uUQ}ÊT{¨Óp6µ£X–VáT4¾t¢DM«XƒEÖ¦pÕŒU]¤\Ùª'·òȬ¼&]=yÕºJê®A+ zV°žô¯Yi.óšJ¦ªõ ‰¥U`2ØÂêU|•,•(‹ ÆfÓ«óêZ7Û©Îþå³,-¥XZD¶&– íHѺZ×v±ÕLíhm›ÓÇê =îîpE ±_ÂV&²ýí1:&1 ºÑÍ­¹åjv—~Õç„2Ô] mȧþÝ /aÖK¶–µÓe§[®êÂäºÚ#v ÄÞñ¾ó¾l,/b}Kß½ÊWGö•æ{­´ÛÚnôÀ;=”€“I`“ÄW½Ð0‹|Üö^ÔÀ‡-*sqêÜÙÚ”•þP†!‹` “N¶¥ˆ[DbÖÊñ¼0^jáü¾Ò¿™p%#ÌY6ÄNª‹9ìÔ#sÆË…$‹CaôJ¸Çèü1fÓÇd8YÆP~²•lH+_ËHÖr–¹œã* YªDîïŽûåív¹_–“šÜÜônYÊpž£œ'f;YÌefsù,?wøÎdγ™•ˆæ­Ò9ÑÎá”ýM=§±Ðþ9´‘#ýgA»9‰™†È¦ÍÛiDWIÅA¾±0÷»á5ƒÚª°¦0¥W=2Qó Ò€öô¤/½èAKðÖ!5ë|êOãyŠÁnL®AùkgÊzE©¥²Ål^3ú¡´Hp‰Ûmõ÷ÒÎ$µ2lW›Óáx®tÙýŸZ_ÛÙ$7I¬ÝKh·ÙÝXƒwù]!ðÒBÜK®ô[ë=ßxëøÞÌx´ÝàãµÕ(Fw©uml25 óÃ[ðXçûãŠö†Æ…Õëds¼²ŸëÁù‰í#pÛÛÝw‰|Œ¯åžxo'NlS§{%í:hMî’c:ç¨Ý¹‰iþÕ¦‹¶Qèþw¿ÿÍ[¦âè]œ·’TÎc¯<áÙ û³¿Žˆ¬ŸüÝ!]zÍ_,i{—ß"7øØ©Rt n}æ–øÏ)Þlº#î†8{¨“Û®ÇÝís¼ØOv¹GËî4Ä;¥J—·ôòKÍ/ùÐ[wôŽO|ÈgoúÒ{ýö‡h= '¿™Ø£þô€Ï½åWŸÑÈ›ý檖òÚÞö]¿}ø¤¯=ûäOûõðý½ð«OüÔôøˆ¾Ñ¯?îì˜ùP?1ÏùîóŠÞßg}ùžvš¦Ÿ¶ë¿z‘ûÿà«^þü`÷‚Èþ ÿ@Ìù,þúºOúÆöÐfÂÐÎïÁ¶ÿü6¯«$°ü80…(pİÆôíøÏýü³ ëNI[Œ,áo!úpPü<ò^ÐqzôŽýÞ¯ÿnm‘†þ¼,›Ì3°ø0/ 5o 9/Ëb 7®uk© ¯ ÁÐ WÐø|ЛO6`.æ†kæ°® ¯ì YÐÐûÈP ÅPµö/T„®Ý¶P9ü°¡â° «pç0 ‰0Sá½Eê¦Î»ª.ãÞÌñí0 -q £OötPð1]¿3<;`<ÕÓ>ï“=ÕÃ=ã“?·s>×îS@Ñ3?Ócþ?û“?ÿS=Ès@´@Eà@>4=´AôAEà.t@4B%Ô;)T,”CÕ3CI´D ´=@ÀE_FcTF@ÄS=4EM”=P4GËÓCà=AT>m´B{4=OÔHUT?£²4$u†*¥R&«r®²~†`_ŒSK·”K»ÔK¿LÃTLÇ”LËÔLÏMÓTM×”MÛÔMßL‘ò1brxЧj²4ò-‹À:cNPUP•P ÕPQUQ•QÕQR#UR'•R+UPC ú¥4¨ æzN§~4’#=$oËTÓ§}Þçâg~®4 ßþ‡ OUVg•VkÕVoW£}ü±21c2 „1Ú‡a€ü¥òçú§/„Uˆ•æ@¨”/@ðg€DàX GYåÃ"=µ3@mþ4Ú/H@b2€‡ =`#5U.ÐU]€]‡àt.&=â†/Æ ìÒiÞ5^åƒNQ`&5C\{53LÀ^‰à@cJ &@1 ö^…žTÌ•,äN ]9–b…€p06>œJ™43&Óa1Cce“6§4c#v:Ö4¨rdkvx¤u;ž³r¢33Äõ} ¤”1fe‰V& @6dq–cõ@þD60fÓpHÀií'jãã5‡bcs¥& m8ÀÀ.ƒiQfeÓJãmQ`4Öæ^¤Ö- È n«+¹Ã6UÃ'—–jWæÖ/ ¶X‘*1'0(€h®Rkcr ÓrMC€oßâm½6t¡¶t¿¢nscÓrR7l¹ƒe… JeãtV·-Þv6#Ófé–j3²p#†|Va €wg#aUF6wq£n'¶b/öf7Ö*×/Žwr%ödQ€p¾—;†:Õ65(ÀÜfmÛV1>À(<ÀàÃ]A^ sÛB~é×~O þþ>j™·/®[1&BÒ]?àV>èå}þ±34@`¯öšó @@êG[‘µ[ùƒ¤Là À( yŸ¢¤‡¶5YsÕ†o‡sX‡w˜‡{؇ˆƒXˆ‡˜ˆ‹Øˆ‰“X‰—˜‰›l:à ¼VN >À‰· L cJ€ƒ?Vú÷1#€‹ÓXâö…‰ ‹àÛ8^Wë8 æ?s‡àZ#ø^ˆ2@(€b®Ôù‹¡cGàXß v¾×k5 Tø¼–„sGÀo 45@ ׎KÙH &5@Fþàx‡ À:rŠ ~>@e°ò®2äW•µc o ’a`@`t×k º‡ÎæzŽ×2À{ +‘WÙ”³™–“©Xì%TxýV^×¶,xGVèx~ˆÒw´XN§¼h­¸›cÓI‡€šµ™Ÿ—›Q •… öTàoærŽW{ð>à6g~ÀMkSb¼Ve›ö™ [kRS÷¹Ÿ?šwš,`O š… &µç*eX~<ögÛ5:+Y¦70W^ Y<¤{º@ „g¤QàZ‰H¹¨Z£í> =â¦Q`¢‡@\þ%&pd:£szpyÚ§»šþy¨“º I™œ?´‡¦@gº©ñ+¯z4¹›åu_꡽¯C!‚ZsLZœ_lÙƒëç*% {<`îõv‡GˆÙ˜™p0’@«ëÀ©ù5¯C»ÀÚ›ÿº¤InY&à*ë¶u:íò±¥’%™’-£û6{°P[´ƒ»xv ˆ[¸Û $ #=`ŠmZ ¹£[ &@…`C«À¸¥[»·›»»Û»¿¼Ã[¼Ç›¼ËۼϽÓ[½×›½Û۽߾ã[¾ç›¾ëÛ¾ï¿ó[¿÷›¿ûÛ ¿ÿÀ<"‚!ùd, X… 1"""+++444<<<Mci|CCCLLLTTT]]]bbblllttt}}}ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@”pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›´Hˆ D'þÏž;BâÓRÑ4 9ê†)΋  ÀKØÊU©ÁAG™-4ŠS4"  j`B‰&[h%‘äm™–¶5óÖïÓ‰e#Pp€¾IÈ@™²†! €@!B qÓÑXM~2ºtš 8˜0¡qLòÑêÚˆá1€ÿ4Ãv8Pã‡)ž(BÀÐÈ‘€ø°ü«VÜ^ªù]üOó#ˆxøb‰î"ô.Ò=MðãhÚ OÞQJ Dw¢€."<0Õ¤†ÙXHpÂ{(€E à•j?©åÜq#8@ þá @L9!ú ¢ˆ „gBSÐ!:^™`z©××N#<°áŒCt€Yh …%„€àˆ ‚ ¾ ˆ º¦+pvà‚^5ÇÕVPØåQÒWQ\Ùh ‘Iô7A ˆÙYGp< ™kh&Á áf€Þ~k V)4ÐÙg`êtäD B‰HJƒlaŠ@¡ŽÐ¨ĶÞÍ=WÄ‚àiDlG¤7ë© €À¦¤)l N` hÇV€ YD&šl£¨®–Y„:@i:A»ê´¹aí¢þØFB¢PFAxÏ ëgºº¹L€€Kîv&”‡Ä cÕ é~vÐì:àšƒ¡¡À©°ï~‘F¸_{þÄ{ aŸÕéô ‡ƒe|ê„&ˉBü'Dþ!Ñ€«ËGl•?MÁg¿ò"Pµ\bè@œ‚•:Ä÷ 1±_<_OO> šÑ¦6XµÄ`=„Ö\Û 6¤ÌÚûV[q¸D ˜p‰ °á§Ä¤æÁÂGÀÖæ†.A±DL¼ßÄa Qe[LqìØ’‹sÔK2ù¤‚àllKÐËŒlÈñï¿­þÉDÄšfæDŒ°`ûI.€‹kµf[£èáB$Ž[½£ ðŒá8°«©Öüón?d‚Ë ßlÑŸÙKx€À æ÷KDc´ô¾{;'ì.„ÊC¤…[‡[„ !c‹ßЇ?ÉmìkC¨ß"¤£#¤ ÍùÉÏ‚Všó ÈGHxÖÿjÖÈ A+%CàÙx„‚ey'¥³¯ö -ðSMø„°/!4¦7)dYõt2C){)U  “ßá¨l²+‚p>%,ëu[)Ozú%€ÇIíT¬¥(w!"8H‹j[Þ`>‡EÁ„±m(šPn¦¹#ìLÿ˜þ?€‡Eø" ¦X„}Aæ-7²¨˜ÅüA1Šü#B KBo#bDnÈ^a|~Z àõ­¯‘Iè ýìG¶ A…Ô{NÇ€r:"Ð,d…ãɈ€JH±•l­D¡r%aŽ’ €Q†K!ê?ò³#m‰‚@¢@” SñãI!ȇ†$„æHKäüP’Q5à  ¡?{ÂÖN&áxI^ãH©;ãèD‡ZN¥°¤Xµñ-òÑÚtRH" 1ºŠŒ (s"˜{ÄàØB»!؋˙õ¨çMÊSzÜ û·•¡ iü´æsU,ªDÁA ˜„怨(öKör˜Q³*V—F±¢Êe+%áÕ”ÒáÕ'4*3¿¹×¢ÞŒ©cMäœÙR‚hÀtUzàAÞ !²Y”îSšÌ]?ó^Bp€Þü´šøÌl$à€ª˜KQ0€£z§I ȖǶ¨EqM€\Úªqj+àöQÌ"¤F#Àæ>´± ˜ÚÚ+‰ú†Ye¡p‡€¬E-‹pŠÅæk“%þ[äŠÑ8©….k]^ºuqä­"´ðäµ±\qÅô€½!Á‘ܤ·À1ÈÀ’|%=ŒÅ1 ðž™^WÌ"”ÖvJ 0•ªHO˜Ò‚š§¹ `'Ð_"WI¡ OÝŸ‰)0â°&VÅHˆË\žd¼¸õTÖ½ãNH ¡²èÊ|HRÊÃX„XLT’jâÒa|˜™MÖÀ”Là%µMím2~¯ð³„`‰0ÁÐ<­&aÆé :o§‹cnƒŸºZa!dÆ¿%\ Jä¼»%ðiÓÍ3àÐÅ:´?IôHR>2…J+@Uô¸ÜCþÉÍjª Ž7âæ©x‰gš~ôM™ /Ñ©Ž5Lš£QZgb°–µ®UÒTŸêÓ¾‚ :ûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›[Û!è@&£§ÈÑ— ²gIïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†;œß¢…œLI?ÌÀ¿[îh‹uîŽ{üãËFÈGNrs‹¼ä(O9·O®ò–»<Ú4ƒ34úã€b‡ ØÀ› ÀæIæW½x62] ¢SÃèÒ°«¡q³×AÏ5~ñ”>tmþ ]W‡Õá±õkd_FØ›Ñuw”½èVO;6ÎζO#âÔ€ûÛ³ávuÔ}×xÄÝѱ÷¼û}7GàÿNx; ž‡/¼âáxq4~ñWÃãÁ1ùÈ[ž •÷Fæ/Ïy/lžŸï¼è±zm”~ô¨—Âé×.ôÔ»¾ «¿Fì_Oû©·þ³¯½îEv{³÷~÷À·Bî©1ü໾øIÿ½ñ—ÿäGÃùÌç<ôŸ1ýèG¾údW¾õ·oì3ÃûÜÿ;ø•1þðã½üÈ@¿ùe­~c´ýš~?1äÿ1Ó_÷¯Kó þëˆþç øö2€¼`€˜¨ ˜þ€8Ñ€¸Xh 82q´ ø( Ø,‚°@‚"˜&è )x‚&±‚¬à‚,80¨ 3ƒ Qƒ¨€ƒ6XÚgw=¸ƒ¢§ƒ¦ „@˜DH GX„‘„¢À„J8N Qø„1…ž`…TØXÈ [˜… Ñ…š†^xbˆ e8†q†– †hlH o؆þ‡’@‡r¸v yx‡ø°‡Žà‡|X€ÈƒˆòPˆŠ€ˆ†ˆ{?Èw¸ˆ±¦ˆˆ ‰¸”h—X‰Ž¸tm÷ˆš˜g™H¡ø‰ˆç‰å0Фèx¦XŠœ˜Šñ·Šã€Š®ø ²µþ8‹ ‹ªØŠ¸ˆ_·è¿Ø‹¬Ç‹éŒÂh ƸÉxŒÓ°ŒyàŒÌ¨uºÐÎP†7ÖhØXݸÉðs Žàè~ÚH‹çXŽ1AŽqÀŽêˆéØ îøŽý¹HŒôø€ö¸ ó˜½ÐmþÈ€ûhz9-xÈn"kà Y ™‘ ¨²‡‘ùy¹‘*¨‘Õð‘ ù‚"I|'Y’F˜’ɇ*9‚,ù|1ù’A’˜7“4)69;™“Hˆ“×”>é=E9”R(”ß§”Hù…LI~OÙ”d•éG•R™†ViŽ.y•"q”_à•þ\™ `Ùc–k˜•ÅP–f ‡h9m¹–xø–ð¸•pÉj©wY——¤'—zi|yù—‰è—õH—„yƒY‹™˜˜h˜£pQq`WóƙԘŽ)Š) $ ,‹ãnD ( Å&ý¢™›©wI õ#šC (ø¨š«i‹­9 &pEG@3³y›µYŒ¾ ¹Y™£©ŽñhÅ{ˆù›Uœ ›°y?ð!°S‘œ´Éœ€çœŸN@ÐCÙ©zp—Þé­5eÜIžÔ¸žÀeÄ™éIöpŸ wì‰턟1t çÉà)žî¹ŸòX ™ðþ (à 8Ôà  שžËi  Q–éñ/w±8¥¡,d±Ã4žJ&Ú)z¢íˆ ³°¢,ú0ª3£lP£9æ¢6Š’Úž=º£n¨£±€£@ê‘BZ‚GZ¤Aù£èȤJÊDÚ}Iú¤Ë¥J¥Z8¥­`¥Xú•Zj’NÚ¥÷À¥Hó¥bê–azgº¡fJƒmº¦‡d §}™¦üø¦túxz sš§Œ¹§C¨~‚ú“Àmv6¨M*§g§ŠÚ’ŒZ0©”Z©–z©`…ú¨a¸©M€¢:ª¤Zª¦Šੜz }j¡zª°jª©þꨫº¤Œúª±š«¨ªªµÊ–´Êz¸ª«°:«½ÊŠ·*¬¹J¬Åº‹ÇЬÃʫ˪‡Ðz… ê¬Ïú«Ñz ­Z­Ö*«Óš­„ø­\È­ÝJªÊ ®Š­ÈH®å*ªçŠ®wª®#É®íú®ðJw⺠®Ú®æš¯÷ZÛ¬õê¯ÿ:˯£j¯;¯òŠ’[® »°ÍH°fH¯K± Œ{–Û­›±ÒذÛ±Öú± k«\g±»±'ûŒ,Ë–$ë¬&Û²Uú²“°¯»«"K³h³u¨²%ë³<+›³3;´Ú*´Ò³Èz´H›–J»—@+³Qû´7Zµ€³ë´þV;—ͺµXÛµp¶‹ µüʵbû E ¶;›¶«°¶gK¶nË“r›f;°m;·9X·“8µMË·zëy€û˜L+¬h¸¹·x‹¸Zùµq›·Œ; Š{±¹¡0¹+[¹–û ˜´š»¹Ð¹Tû¹ «¯ƒ °~k¸§[ºS ºKº¬[±°Ž©««‡»¯àºª;»¸ë«Ž»¸½» ºk»«¼*Z¼¬Y¸ÄË»Æ+­ÌkŽÊ›¬ÈÛ¼I0¼Òû¼Ô®Øë–Ñ«·›½|:½¶Ù½× ¾(¾p·”k¾‰¾K¾§ú½ì+¹îÛ꛹óû¢õ»ðë­Û›¿œù¿ðØ¿¥*þ¿Ì¹û«Œµ{½Œ¤ ÷ë¹ œ» \ž ì½L½Ö‹Á<Áö›ÁxÁ£ëÁ[ Âw ¯KÂ`ú»ë«ÂnÚÁj{Áåë©°Á3LÃá Ã(ÃñkÂÁkÃ=¬Ã8Œ¢>ì<ì¿CœÃ,Œ¿I\ @ŒÄMü“B|€G\ÀEŒ»OlÅSÅnÅýºÅ\|µ`œ¸UüÅaü©cŒ'°Ð|ápe”eœ°W»ähËr`{®{sì®u̺ä#{#tÐhtKÀf|ÆI™Æ† m$t c«ÈtìÈŒìǘLi†BB÷”l¤– È›œÉb@ŽíG{“ˆ|Êþ¬³¦L­¥,˜E0" ¾æÊ£ ˱<®³,7s`6vòÊüËvÈ3 ðú)ǽœÌÊ|^|ÉÕÜ©Á<¤ÈÌÌ KŽ€ú‘ %Í9ëËÙ̪ÀÎÐZF‰|ÎÔœÎëÍD`_Â’ -ºyQgdR:Íøl¹Ù­|G¦“(ѹslB¢+(¼»ö¬ÎÛl“üÙVT qeJÐy{Þ–¨ÍyÐýÏkôhÖ5ÑËëÊwÑ¥KŽ € °Ó˜=M°ÑH À†'2ÍÀÒŒ©H©šjÓßÈ:PÝtJ F3þó4]EÍÁ®|ô\Ðäiž.mP}<1Þ³Õ7ì¥^m´`­bÝÐ0ÔôÙÍykkͶ'-˜E|s| psMÕÿñ)ð66׌ý[Ï( wý¸yýyŸÿ¹ ÷¦ÑczãÑ Í{"ÝÕ_ÍÔ›Û Ú~½Ú©ý×M   ê úT<×f?§ž£-Ç“ ¼•ݺm z&*Q-#]Ú¤ÍÖ¦mп-ytÜxÝÛ¾½Ü{`P\ÀέÃËÈ­ÛÝ-ÝQ@Ž% "Æéå hÄÐMÙàÞ çsqk<É9Ïʽ޼ÝÞN@Ž€­G}ÆþÝ÷íÝ®ßûÈ#"V3’Ñ¢üÝjíà¾àŒ '`DfÞPŒÞ’ á^½ï ! B]É~Ì»ÝÂ.á,¸U[Ñ‘F–ÏMàÑÝâ4Èhuëv“¹Íá+ÎÄ:âÔpðã~ãBžâE^¦GŽ`€< ^à®åQ~¥Á0•¦Çä[žã]îåÂp>÷(Îå6>äœæR:å|@p`oŽæ‚+ç#Lçjþ k¼/Üj^jæ}å]NŽ @.JnÒ6Žèq®èQNŽŽ1õå|>épNçäH2êägÎÞ€.åK<çOÞéiþ~ͤŒß,^êAgçá+é*NéEÎêè<êù]ê¸Ù.îç) ëÊyêžê›îéÍM‘¢žèª¾èɾ礮ë¯ÎëÏ®éÑÎìǾêÕÞä´¾vmë:Þë3ùíÍ^éÛîÇÝŽ—^î·~î@î·îÙî첨éžä>ïæ^ïN¼ìœ®ïíÎï?yï} ì-ì±NìÁnì×èâ^×òÞðÈ.ðôKðzmð3ðïêDëã¯íÿ©ïoÔÂþñ ¿ëïîö ðï®òÒÞñÔ>ò—ëïµÎîáîòó&/óNóØòô®ð¿òÓÞò8Ÿ”%ÿ§'ÏÕïóþæÌóAoõ3Oõe.ô¾õi}óHŸñJoóLö(_ó¨îñZè\¯zÏòoöR?öiö)ûôTïF¿ïrÿõÿ¾÷ß÷ê òp/ò‚¿áh_ìjßôŒ÷]ßöíMô_øG÷zOù|oùXŸ÷o¿ôqÏù@õ—/Ý’¿ó£?ôk/¸Žïö^?ø)¿ú‘^úþúˆo÷ŒØúîmûZŒð /ú€óoà§ïíŸOö¡Ÿû´¼¼¿È±ÏøÔªû˜üu_öÀ¯ù¿üy]üêÞüØ<õк:oüÞßêàø½Oø oø×¿þݯý'ÍýøNýŠû¾'ýÍGÿ ¿øèïþü‰¿ÿ@€‰Eã™T.™Mç•N©Uë›Õn¹]ï×+ˆÀeó}[0m÷Ç\ÆfñEž—ÓÉiÿ0Pp°ÐðqJ¬/±Ñ1kMOÒÏoR²ò‘³Óó4Tt²Žô”32Soìn•Õu–¶Öö·v1—M6®õë8X¶9Yy™¹Yh×9:ê·˜ò8ì²NXºÛû<¼”Q\œZ›»‹X»-½ü>^>z¾û¼Ú}k]ßþ`@‚ê T†¯˜¿R˜ØÍ¹fbD‰Ÿ¤x !0…Xø¡{xdH‘ö,Ž<•ÖÆ+ó}ÜrBDL™3iÖ¤yÂdNþ;¥”äù å*•VX&t©E ¥K™6uÊ”ÜO©Suú¤ê(h¦¡UŠj·¿í¤óоü ¬¿×»L8÷ øÏ°¹ð. ¼°¶þ1Do¹ðš£04 ƒÀ MD0@ûö3FÄÖLQ?ŸkñÄáÒp÷1 »“¯7…D G u|ÃÁŠdÆ—ŒpÈ(G+òÂ#­iRI+’9)½ŒJ­l‡G$|l¯L5žüñË6oL“­$ <@¢§L(bÌá>Ô2D.=t“PªÂŒ¬6!:8‚8!„˜p"¢Ï$8“?,7]MO  CûÀÑ!Ò¯2…“OP;4>RmÍÉTÀ"€ˆâ,p!(Ì¡ iØÃ±pz#œ] y¸>ú‰(°à …H>"Âð‰2Lâ—¨~çˆy‰`ö¦ØÅ{‘~‘óÙýþEFÉy±‹U¼Íµ˜EÄlþÑhD£mÃÆ7ºqBpô“©hC+6ñ}1$¡‹ÈÇÒ1C€ÄŸ ‡HH(ò~\£"ËÈÁAž°ü "‰DÉ32Ò‰Ž”¢&=ÈÉÒØ1x¬K÷HÊH‚ñ‚büœ%‰ÉGº’‚¦$ *W©GMÕЗ’ÄeÆt9%OŽ‘–¡´å(‡¹¿bV†—#R¥43ÙÌø=S2ÑtQ5±ÈMk–›‘Ñæ{¦¹Í[~~á„Ì8L òk€èÌ¥0)Ë‚2Q¼¤<ÁIÏNÚÓvfD&>ÉÏtúó”Çœ%A+ÉÐOÔ|ê ;³TNrz¢¸“(˜zO}Ö’į̂3ºËŽ4þ™ùå>GZ¼þ…¢ŸéøTÚRã½Ô/1¦EÛÉS›Æ §o¨øºÐ*ó§· j[tZ,ŸÊô©I ÚRÙÒÔ}±ò—NŨTGÕµXµbQÕê9¹*:¯^¬Á›iQ=ZVÖÕP'%jJ ºRºspJZÍ„UâÁS‚xuiI9Ôöѵ¡G­©`»JXhÊõ°¨bëÊØÆÂ’‰†'[Q*Y£Zö²¤â«šÖŠØ‡‚–qz•ÊhTZ϶µ@ul6!»YÓNÖ®H­ÐTûÖBP¬Wuíne‹Ù?j6°4­lHƒK\Bõ–'¿½×ps»Xç² º;‘îËEÎÎõºþYËnUj›ÜïF–²‰ oËÆ‹«òz÷¶Ÿ]&K× °öšd»è¡î|ïZßrÝw$ùU¢_ßI`ÿŠw¶â|o•«^þêöÀ˰HÜ]Ÿ×¶ÆØ„™µàV¾¶³é=­†ÉÅaTØÀ ñ²`b¿X—⧉/‚âý2w«…JÊSxÜcãF4ÁëôpV…Û\µùDk8Ë’Ï’–÷óÉõD2i§ÜÚ*)Å3òD‡ü×,Ƕþ2:iL_yºhÓ˜¿Yfsu¹À7Þ!Yoeá·yËE.|A ^ã¶Äl¶¦›%ræS“ζ²3‘{Hèˆ:Ñw<´›g³Z:§pþ&`ˆ«»\eU:ÊÚs4D ÍÌnFº -†ñªe"ã7ÉÙ•£˜¦U h£z•=ÖµSB½5A7SÖ)5}Û8éè„…ÉÉ‹“9 kR[ Ãîo±qhSûö×Äv@¤ aj_[ÒÕÆo¶q¹míùÂ`æôƒ­{jpk—ܱƴPÕ¬ßzØÙá~7yó Is»…Ö6¯ºýÜiǵ߆ü7INpô|ÅsÞ÷TÍêU»Úuñ~ö¼™Úð>?œÝžv7±³k“+¥×ú&¹¿9^UÿY¾·ž¸9g~le+›Ù³5D>n·ûÛ+ïå½w®sk²ç.{9Äm-q¡[ûé4þ:"Ю唖èGO¥±{ÊõŠzý8ZWxË¿ºôÜéÓV¹Ú·.nª‹IÇÏEt¶ì;Íû ª.O¹ÃƒîhùÝ¡Nx©þí ;ÙÑjv™O½ën|Í'ù¸À]ŽGà#>xo¯ýóm§üåŸyÆÇÝwfzÌÓz¼Kþë°{éçxú½:¾õ@½îEoy¨î ˜¯ýÕ›zFøaM>òe¯÷æó]ø^Ô|`pïyÞ¿~ô¿~‘—ÿ‡¾“Ùö«­¾Ý]_øòþüÝþùh/ýðcÛø^nì³ïüúõþÁŸ‰OÛøÇ9ëöÏþ|oï¾ÏúOþÁþoÓ îì:ü®Ïü${5*Îâ^ ãrçý¢küv¯î@Pð"0{ÏIïÞÞääLN££¹Œkõ¬¯±ñïõ/;ÙnnÉr¦Ï>ÐÓ¯òNPûòOùÖð>¢ïò^PÏ »Ê¬ÐʰР°Ž¸„0ä„Nì$ L`W@@MbPàø æÂ,÷jpå°wÐå,0ø¤P ¼†QRÅNÀ @À@ ×F Ó,¹Ë q ÐQpaªóP;–fœæ6À gñ­ õ'l°éhp¯ ï•a¢ÏGþ"fˆÀàWŠ  €4`o‘ û —r ÆgSÑ Œ‘#‘˜‡± ;à?&B†²…På‘ .ÃÞ¥ !PÑqÉÑßÁM#§qÿ!]Ö%aŒ $€Bò†CFQ ™¯Ùäš‘¥àÓñ;3Ð&\-!Á C ?¡`ìQa†àòqûRrq"Ò[‘û‚ Hr•+V‰@%ë0gíáY¢eZŒ`¤F¼±øq,@(‡’(‹Ò(- e&@ šÒ)Ÿ*£Ò)A`&ò¤Ž+2);`)þ¥Ò+¿’*eÂ*³’,‡r+g¾R-¡2,cb,Ë’,ÏR&˜r-ë²-Eà-á+å2&è².Õò.Eàþr-ï2/õÒ(ùRü’0¥20³1Ù²*@À2/33S3@”R&Ò22“& 34›Ò0à*S+=³/K3*Ó5%S,qQ;$@f†às&yq|‘o† O\R8‡“8‹Ó89“S9—“9›Ó9Ÿ:£S:§“:«Ó:¯9_Ñ80±iž¦81=ÑL *Å!Ï=ÓS=ד=ÛÓ=ß>ãS>ç“>ëÓ>ï?óS?÷“?Ó3:`O´Ãk@ QÂ&þfø ` Q7IÌAáçì& o à…À ànÒðA;ÔC?DCTDGt-"t ý19ø±)jR"4Q€ø¤ç§6\T`”,”)x5@þf€D`FÆF‡„ ô:@ aÌ365H@ Q2€& = ´4¨ÔJK‡ f(%&ì5ÅÀ…`иt¼´F¸4Ñ:œE«ÃÄ”@.…]&@7þtL…n“8ìôªTQUÆP…¤q“6«ƒû49U#92}´6àºÔ6ÂvlËöJ`MAàzgkÉ4*%µTmÙvHäänÈð:4ÀM`'Y´ À)øÆHi4IQƒq›¢Là À( gý¢ž‚»Ž´FI”tK×tOuSWuW—u[×u_vcWvg—vk×vowsWww:àoà tU; >€w“7L  RJ@qSUö ”{»Á` s£ {…à{a6{Ç—e)€`‡`H?ÀM `CÀM€"å7=§aQG`F? ~ÇþiuU2s?@W9@vÔu" @›B^É—‚q8QD`fv `Ü‚7ãÔN>`U~ñ|1Ôö~íeQ €\Õ0Q`@@`UuuàlHaÂff ÐÆzŸ¡yÏ´‚“Øøt…WèAàz…6¼[ ‡]V5UGQýPy… fGWE•x›8#msŠX‰Û˜˜8X à¿Ø`¸qf–l8±pBP»ØÕî·#EW6Wýpf“””ÝØ‘G¡]ÓwŽ™&¸‘Q€ÉÆAWlõf³4' ”ÖKé·f§ø‘Wþù@ ˜F’Q`H‰®Q˜1‘1> &Õ“Q@‡ÀIa¦õŒãNùB/™•›¹à8–gy*YÔ5@1ÈF”ÀTiy—Éô‹y”›ØKó„oðØ™ÓBà•…@ŽQ §ùIØL˜qùÆ%àl<`ÆTd›Fh؆qxRïñ`ü`e •yxMÕ¢šž)y‚Q îW&ÀeìŽt'ñŸ€ãw€ º 8™‡`¡Gà~à¢#Z¦Oe¥ ¦g§ßAÑ‚—”¥àsZ¨Ëa0wB‹× nz¨—š©›Ú©Ÿª£Zª§šª/«Úª¯«³Z«·š«»Ú«¿¬ÃZ¬Çš¬ËڬϭÓZ­×š­ÛÚ­ß®ãZÂ!ùd, X… 1"""+++444<<<MiCCCLLLTTT]]]bbblllttt}}}ƒ¤Íÿ‚‚‚‹‹‹•••¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›³Dx0D'þO>{p %(%£i‚ÔMSœ#,@€ $–p@@µ@ƒF4à@†@H¦ÝY”í¨Z0¢ Gä©¥LÝžyêêÄH€p€¾H8VÜ € DLŽ``( "²þ Z4PÁO@×U@"4ÀÉݼV)LðéŸgF þmc‰"<Ñ)Àô†p`@t‰j{9N†·pà|’A„ƒe°Jn)»ïð¿K‰£ù^„wñŠ 0@’¿A²ChvÖù-wü5@Õè&DÀ D'„HPÄþ GÔh Žd!à>‰Ð€|`†lháo*²¸y$<Àâ'&¡Sä‘uK¨·a{Ëí$,šhÄe~WcY¡„”àÛS¬(áºù4‚˜d)d„ ziarwʼnYŠhªyE€^67†þ ±všP‚k±€iCh0à¸æ@Ø  ÐAAøfÂs1Q@|ký„”p°€X¦ÝiQÁ õœ†¢Æ«PêXœF Y"§œzõˆ„kG°W¬P ƪe˜ уœ°w:-À+X¦¬ ˜bËé—þ¨EÀ¤ @·Z·È.ãJfn§ñ}€©\ yÂy‹¸âÞ D€]&ñAPÀ¢ð¨ ùíu„0ºÁ¶î  Bh䩟ZÒ4Ê Ð×"Bh E8„.q„´ „H¸L( OØ„(…°@…RXVè Yx…"±…¬à…\ø`¨ c†Q†¨€†f˜jh m¸†ñ†¤ ‡p8t( wX‡‘‡ À‡zØ~è ø‡ 1ˆœ`ˆ„xˆ¨ ‹˜ˆш˜‰Ž’h •8‰þp‰” ‰˜¸œ( ŸØ‰øŠ@Š¢X¦è©xŠò°ŠŒàЬø°¨³‹Úþ·~à‡‹¶8lµhy ¸‹GÕ‹‡ ŒÀHz¿{ÇXŒ+˜Œ¶ÇŒÊƒÎÈ{ÑøŒ58ÂgÔ¨ƒØˆ|Û˜?ØÎŽÞH„âh Ä8ŽÒ¨‹æ§Žèø7çHïØŽÜÈŽÆHòXñ(ùxæXŽñçü8…iXXûgiû ¹ˆ©9ÉyY‘q‘}À‘IY€ù‘/á‘{`’$é!©€#™’,’y“.‰‚+ù€59“!“w “8)ƒ7 ’-Ù“'Á“u@”BɃ?™ Fy”A˜”è”L¹K‰zP• 1•q€•V …UY Z¹•þUØ•#(–`9_égY–_H–4”jÙiézlù–™8—-h—té‰xé“n™—l¸—½—~Y ‚©…9˜x˜7¨˜ˆ9‡‰Ù˜‚ȘHÙ—’i‡”™ ‘y™›°™eà™œ‰™Ù”–ššc€š¦¹‰£¹ %  sEÃF§š« Š­© "À+’p3¤ ðm @¶y›¥˜›œp(¾NêXœÆ©ŠÈ¹ $À7XT~Îùœ¯š0²©UÐá VX£Øù ×ÙmÔ¹1ÐT!žçYž¾XšÂ0˹·Fñ)Ÿ†°Ÿ&`ŸOÀ$EàŸü9þikÝyþªŸ€ ð  àxŠ¡Gt Ú"Ÿ6CŸ*ñÙ) ÀÀ9H²À€ï9 äù¡á衾À¿S’±œA°ÖH*£I£3 ¤q£a)¤Dª—HÚKš¤£h¤¯ð£N“Pª…U:¥"RŠ¥v°¥Uà¥\:`:c¦pP¦Q€¦fÚjúmº¦†y¥kÙ¤p*‹rº oZ§g§LÀ§zJ~ªú§a0¨êG¨a¨mw§ˆz—tJڨ砨F@©’š–Ê ‘z©ªÇ¨©©œú¥žš†£ª¤©¥¥jªF˜ª„ɪª:  :ž›þúªéˆª³J«×x«YŠ«ü«\Ç«½êª£à«À*¨Âš˜ºZ¬Lj«Êš¾J¬ÍZ©Ç ЭšÊ¬ÖŠŠÓÚ‡Ûš­ÃÚ­“™¬Þú Ï ®ãú å*®çj“ê ”ëŠ­vÚ®ïú”òú”8WoŒ6¯,‰ª W¯úЧæzˆ0P°{°›°@û¯AŠª`;±[±kаËå±û±›±þº±§Ð± {²+²$›‹²(«²+K®› ùá±.û±0³û*y6{³!;³:[”@{ 5ë³8;´AK•#ë•гF›²H›´gµ¬é´O‹±T+µlšµþ¸iµO›³Z{ &{µ ¶aË´K;–^k´f{¶Ã0¶d+±më¶Á·q;·tû vK¶x›·Éµ‘P´q+·€ë·©Y¸ ¸ƒÛ·†[™-;¸X›¶Ëš’K“k볌;¹·°·W›¹š[ œûµˆû¹\ºl;º¤‹}•[·M ¹‘›º»ºwy¹7ë¹°{¤»¸¨{»V`º˜»»¼ë}²«·­ ¹¶¼sš»w ¼È›¦Ì›Š»¼ÃÛ¼ðø¼¾H».{¼ÔKªÓû·Øû²Ö»½K໵¾â›䛽æ{¾‹Ú½‹ù½'«½ìû­î» ÑË·ë;¿×ʳ®+¿úË­õ‹”ð ²þûþ¿áª¼øÀÜ¥ùk Å«» ¼Àt¾àÁ¬´ܹ ü¿¿¬¿LÀ<¿!|´|ÁS{ ûÀÒ‹Âd8Âp¿¬Â.,—4 «,œÀ5̽,º7¼Ã Ã9À& Än(Ä™Ã3lÄ­úñ Ã>ÌÄsˆÄ«ÄQ,ÅÈÚçëÄX\¨T¼%  °|Á†¥{V¼Å]L­_œ$Ð Ö? PŸ™Æ¿ËÅkìSý#sj€jÇå‹Çy\ºmŒà92çÄD|±\ÈÙIÈ|'r(ýÓ¼§‚¬¾”,ÉX0•ü‚Hýs€|¸ü³Ÿ ʽ{ÈGþpЇ Ù†Êýëʼ;•%p?Ž1k´×ÉÌʩ˽å ÐêÅ©\±‘LÌÃhÌBÛÌe+Í©[Âlͤ;•J¡£âÍŽœË« ÍR°ÇðàLRÈÔ µãLÎP—$pÔf¯é-éyk÷eÒú΄ÏòL~ÝpÊ»11˜rŸ†ruP¹Åj<Е ˜ŒüÜ™Cra„Vs6ЯKÑ”k ×жϨ¶pê%Ñw,Ò#ý € 0Ó©SNÑH0Û6.,=È.= S)Ó0Ô´µuJ¡øŒlBu^=íÉ?›ÝHmÑ–lÑÓÔþÃüÔÇÕŒ0ÕÚFºÆ  ýÌZ]Å\ S—ÖàSÇÖFa  7 ç ùA × Êd]ÖuP¡z¡»0qÒ3ÄÑ4£c­ÍŸK”ðØkÙÍÖM ¢$j¢œÃE3gh}[b-Î~ g6ê;8:NýÌ#ÿ Ú¡=ÉZÜÒ­½XI ]€ÎÐ„ËÆËØš;•#À"ßéáÅ̬ÛÐËØ€FšVî)žº ÁÆM‹Œ]µy€ib€Õ<«%€oùÝ¿ú ·ÅmÍÉ‹=«óq£´Ü\Ð4Ð=Á|ÞÙÝ´ {ß Ë°ë­³SÙ`&pþ “FÜ»ÞPÅ ÞíÛ‚µ±Vw±-…ÆôíÅ ~à Ž¾ŒV%Ppu\áh|áÐá þ `nÞ ®Ç ¾â$.«À³á õá.nÈ"ÞÂ/£û!ðjÐ!güÚ-žã:¼ãû+ TzVß Nä-ŽáJÞ¡Å báLå!~ã$®•̽0ë²kî娇äK\å0þ 0/fñÝRå,ŽæW¬æSéàÜ*ÎåRNçmçŒ-ËoPäEláfÎàØ¬Êõíç° è=Ž®Â¼Ý‹~èàèÎ,‘ùÁè>­æám§rÞ™NéÑméÕ|ä¢nܤÏ“þÎçJžê½ê#î诽é]Îê;îê!]ë±^å¸Þ×}:å»Þê¼=µŸŽã§Û½ŽéÀ®ã²Î¿¶nìÏþåÃ>è‘nä°Îì¼>íhYí…®ëØ.ì>™Å¾¡ížì¦ní[;îg~ì­îמäÍîéðN¦ËNïÙî@èÙœîÁ~ëì.—ùžæõN}Ê_çû>ëNmèê^éÿÜþïóŽðïìí/íü.° åþñç^ñq:ò­¬éŸðíàïŠ.ñ&¿î!_Ì*/ª,ŸÕ.¯}7ï}9/éœ.ï2ÿí_óŒxñ1ïíúî¯ó}~ï~-ôJ¯ñþLÏñDïñWòMôSïðUoïOò ÿçö3Ïð?oíAòió—ðYòFO³HÿöïõE¿õjßõdÿõ õΛöÝ^öö+øZ-õOoøÈÎö[~öb/ø¿ös/š=ï’Ÿô„ÿòu_êwß÷yoõKêcßè›Ïóˆï¦¥Oë§Ï~¯êC?ú/®øsžú?Mû¤ŸùvOùz?øµÏøñîø{ù‡ü¡ûX/ûZ/úTŸû¶ïÒÈOî«ßò­~—_ÎÓïô~Ïù×øÏ/ÒÑÿîÆõÂ?ßß?¾ÙÏõÕŸnïù±ßü³_þ6>þÞOÿ‰/ÿî|þƪûî¿þó®ßý@`‰EP±,™MçÓR€ŒUë›Õn¹]ïÉeóV¯Ùm÷I}Ïéuû@î¡R¹I‰opÉïî1Qq‘±Ññ2Ò*N²ÒÒ IpÐð-p“sêr”´Ôô5UNtÕµ2”¯0IvöUw—·×÷÷”xØ3ﶯµVð¸)—ø:ZzšX˜úÚ+–¹9¹ís›Éy­¤Üü=½»ÝýþÒ:žO<ª›í_\ @@ ø§^B… v™×ðš¶~úÖðç/M¦ 9vôø±ŠI–4qäÉa/¦DcqÆ—¶ðåC¨gNþ«îôÅ2¦Ë30™ÉJsâMŸK™6MÔÓ©+ E…bBÚRé¿«A³Fõúl¨aMM=fÔê2¬sˆž­Jn\°cåZ2{ m™¶xß’Ù++o]ÁƒUÒ%üè.à¾cþ‚ Ìx+Õ®‡)W^hز¢ÄŽ‹i¼é±çÈn'g6}zfÔv6ƒî,v4ßÒikÚ\}74Õ¹kÝ[[L-W¶±Ïæ}¹Ý×É­ú¼vhØÁ%3·~Örì°W‡^S:˜Ï„Âo7Þ¯vô\Z“Wï8gãé©“^Æ÷ù'¿‹ñB™²údãÁá#PA,Úp8‹‹0:¼0¹þÝ0ì¯;û¾KŠÂÿÜDæ4,‘ˆg±0 WQ¼ø\ƒÅW;ñ¸  8°bƒ‚H(BE\XtPF÷hÌ&Im|r;y[@ >x€ ªÐ @ø vRôÄS¦Â%¡\s0)që Ë!`K¡ŠDFMöš|1Ä2Ùü377o{@"‚üÀˆ K»ó‰ò˜”P¾>Ï”ÒÓ]퀈€ Œàrh #uÂQø QÒç*}Õ²KQ+€"F#< ƒ2 €RÇìð@3]õfO#ó„•Yd=Í€Z‡¸5W,D "ÉœtŸdñlÕ»fÅ•ëþYÓÐtN5ЂB‡M\ÑqÜzá*7³0ô­vµ% € –OcÃEvU%í]˜© x˜ެ Î…X€Î,®Íö]z –ax™)|3£RôÕÒ,G¸<ÀXa‡8•›eµpQÙx‹%èœL¶LG}Âe!"0 €p@S·=¶Û„œ×Ï µ.ihqgâþ¬.kn·>[¡®ùûº°«àùÛF›îzÔÎmÛäžpïHëþ;ž»ñË;U=Çî¹oV_¼Áï#Üm©»lª¿üÇ׃\ç½m4rm'½sÌMÏ®ô9÷™ïÊ>þv^4GoõĶýêØueöójwäªEÞxUz7ï÷=~îâ'åø(§~½¢ÏQ }ÌѯOýùî«ç~í郯^ûœY÷Ûûô#‰»ä…_^ù¬ÕŸö¯sŸüáã7›þþYÃ~sâk^þà÷>ùùÃÞغýYN+åÛXA©Qðq Dß©AýY„úÁ` ¶AòO?lÛC˜@ûY, ØB†…¾Ó â˜çÀBð†AÔS‘·ÃÛõЄ4D¡™x…šÈˆ¹ûaSøÁ&^±?D”^ yÈÁñɰ†X£ž˜¡(’Í‹ãÇxÅ2"þ'†UœáØF7vgD»8Å/ÚñŽZlŸ)—F*±Ž€âÇ™¨Po|<¢"ÅÈHÞ8ò(,\€¬g>JŽÑ’"$éÎ×GDRñ“7 %n0I›9Ê1Œ©´á*oÓJ½tr‚¥œ¤,‰Grq—§üã#­ÈËYú²ˆÀ”¢0ÕK6³‚´¼Ñ(·§ËeÒ•ÐŒ&2·XLg&Ò•±Ô¦ ¹9He¢ÑÍ$æ+ÇINAÞšž”ä5¿™ÍvöOš¨±%}¼¹NqÞ“~ù´T·À=°=7Ì0 /¥Á¢{p^#\büv˜•/†Gò(‰Œò)’' !}"2%§ò þ‹’+‘#Ý? `_†  •"ç°‹ H”Ò-ß.ãR.ç’.ëÒ.ï/óR/÷’/ûÒ/ÿ0S0“.Ëp;˜pžÐ¢P¦ÐH`À„'“2+Ó2/33S37“3;Ó3?4CS4G“4KÓ4O5+ó6`Hðc e°„e’Æbpk0ÞÐÖvsq$0 iÓ,G279“S9—“9›ó |“×c«m;J ¨$€H^[¢&9¬;µs€ ä09<À˜f!@€;À;ÿ¤eó><W$ó9D@K.€®%9@[óþ8øÓ?@‘&¾¤læ82à8ànPB=€@À@¡¤bsôÓ:ì³ïƒ”@Äd ®#ET ,±ãCK ?k´E…àZb”MÖÐ,1fñD×cF‡c11Í“9’ty$@7ÍJM@:…" Hô6ÆÔlT` àC­³¥L=eÐTM7d§Åi'zE *q;ÆÔH•”í09Ø40Àv¥Gö7 AM Q‡`QÙÄ «%?J`ÐELUT2U6õIMµ ÚT™þCÌS§QS`Nkä\ÜqKùK(57ÆôNÏ4M­ƒM_Ñ®#VA•SðTi†uM„TÚ0LÇÒY•U«´Ie”U« ŽV·Õ¢±I}µABTæ$AH`œÔZiÔXÔEaT[ݵÚYƒÅsE{”L@_×eT†VÑÃ$€>`P Õ<:€¸Ä8€ØA@;  ”9–KbK`&Ô:@&59è=?LÞpb+ÖC¥hzäHÍ.TT`K¢ ÂCà=ã9nv `H € ÈÕ2À nfgÓiŸj£Vj§–j«Öj¯kþ³Vk·–k»Ök¿lÃVlÇ–l¯c˜N¡L Ó. ÊVnw€Lvjö ÈáV¹õæpWUi•Ô º5p×ÈS  V‡`=+¶G¶ð.t$€K• iàlTg1ÐÀ*×iôµL1à : L·4VCÀRàZ“ BÖ8Äheÿ]×âŒÊ˜à 6è#h POàÜ*È …õIeæ>Ðe”ñ=B˜P_W÷§ØYBd^ lù\Ih Õc 4¦AAL8ă×_šB¸‡i† °€fß}©S’@‚‰T*አ$*@Fà€lçQeF0ž²Q^®Ëµú*¨ )„eTF` P§S§@àÀwFšÀh³6†&@Àc (Z,µþ¶b;ĶŽvÛߌpÁpµÖªÛæCì(§}K”𬑠Á³f" ( èÑ"ñ¦[>Èë—XMšjVªeáO:Ù9 ­T(š )¥ SX2Ch@~BDuçÓYÄ­4'!½ôJ@ïwÄe² Ä%ÝbmYnÐ%«jBž qs|iZ{Ìk† ñ`Ö&`¶ _V,vU{¼À¾ •PBhç³–¡-ÄRðA !,m(,#Á(›&$*àÊ ±µÖe§x©{Lqž2€w¥Ó<2…±ÕB¨Ô¥.Ëö_ ÌÄþÀHX†]fænfIg¨2·¢:!¸Ý:ë& À”q9{–A.y¦Æ¡üßÉ:_=bà®òòt/5Šz7ÁØÚØó®_^Á{¯Dl-ã}€úúUóª¾aϦ¦•£¥ÇoüŸõX56¶ÍdHíŽPŸŸØO¡ñQŠ€„:ù‰Í^~$4ÐI³rS‚í©e0š ó”rL±9X" | ˜¿„0'&œoa–A@G¨$pÀòSÂt'•ð”‡GÉ£PÚ2'„‘L0i鞃Dv¡"˜Ž‹&H õ°s8 iцÛÛþ€&ÝýIƒ]ÔK‚sÂñäŠEàØ^ØrC!< ‹’bOy¨XE ¡‡,xHÉGˆ !l¾#›ÜíÌóq˜ ƒ@X&hH耗„Ø`‘"œNõ0Ö2àkd'Ó* П ðwó‰åŸ4t îɈqþBé tЖÀÐÑÀ“ÃA €àðñI zm¹H¹S·΢ŽMûé¡Z º/Ú¢YŸH£2ªŒ,ªŽ9z£zh£¹£<ê—>j 1¤+ ¤9¤F’JJ Eº¤:Ù¤,(¥Pú“HŠWZ¥öð¤GÀ¥ZŠ•Tƒaú¥ò8¦3™¥d*^Ú¡iªkJoÚ¦vi¦®§r ˜tºƒyz§Â`§È·§| ~ê§Jƒ ¨…:ˆZ„‹š¨ºp¨hꨈרG©’:‹“h©—*Œšj’º©á© *¤Ÿ x”:ª² ª¨ºªºªùЪ®šˆ§þÊ”¥«Ù«¶Ê™µj¥¹*«»ŠŽ¿Ú«Ê7«¤@¨Âº¢™z¬ºš¬Êª¦ÄJ…ÏڬЬN)­Ìúƪ¬¸j­•H­Sé­Üz–Ñš…ã®` ®bi®í°­êŠ©eW®íÚ†ðê Ùz¬ì¯»8¯çН©ˆ®âê¯üê€úÊ õ*¬÷°å8°h¨°;˜»—۰ʰò±‹ {±Xz­›°K Û«Û±ù±ŠI²Ñ8‰#'n}†²Šj²}êp0ë²wH±˜€2P:»³<Û³>[`³4{¤“Hp´H›´J»´PB;´mñ´5k´L[µKë´3 µ(µþ”€2Tkµ`Û´\«µcp¨_¶U‹µd+¨c+ ^‹¶`«¶kë f ·V+·sË uk·L‹·y;±Y+°gË·Hë·‹±m»µƒK¸b¸‡KЉ o˸Ik¸K’Ž«¨‹K¸–{¹Sš¹z ›Ë·ë¹b º9ºv[º¦{¦EK¹•¹­k¨²ë“ »;»˜ûº¸›»ºû¹¼‹»¬û»ª°·Â[»ÄëÆ »Ã›¼•¼Ì‹¼Î £Ò ‰¢Û»¾;½zŠº«ºpÛ¼Ú;­ÐK¹à¾äʽ5ê½h[¾æë…Õ+™×Û»ìÛ¾ûú®ê¶óK¿ ‹¾ˆ{¿qû¾úû§üû‘þ{þ·¬¿ËK¾L¿ ̸ùÀ7»À®¿Ç;ÀÌ ̹l¾Lº¾YW±{Á*Jz½|ÁDû $ð,’7€3fp»*Ì´ o7ç÷†Âœ¶¬½ÿ(]1|°XD™Â ¼Â8¥N,ð!-„7 Ä@Œ½üÄn;ÄQ /u„7ðÃeËÄìÅÎûÐB!`l`Ä}‹ÆÉû%`8’!u÷˜f¬ÁQÌÅeÈ‚°°  a`ÃM,Èç;¾gLÈŽü¼ºvL¼ÿø¡"›µt|µ—ü»ÿø@Ê FeüÉJ»Å“þ¼ˆ¡,$à%Lð™¡YœB€u5&|¬»’üÊ\ ’@ƺ;Œ‚Ë3g@$ÌÄÈ‘ Ìõ[ IüÍ™Ç6PŸ½\¸±<»Y @ÌUQžuo–§ìÁ¿ ÍxºÎ|°Ï p’¡ÈEPÍÊ„lÙâÌ€ÌÎû ñ ÏÀ`tKp E;4¼E5ÅÏêìÏjÙÍb€ÐÂôkÊ…ÎÛ|´® ÑìΓ@ÑÇF§†ÑZ,Ѧ‹>—Òà)ÍÒÔl0€bÎI†26Ÿ¼ÑýÚŸº GÍäãn¿;>§1nà<žâØ*âøKâA¾ßCŽÎ¾•Fþ¿»Š2IžãK~ÛENÛ`:åÏ\åV®~X®ã2Îå•<áQ^â?>ãc>âe®åýÌå=~ågާlþÐn.ÀÜæ>çÍ]çi~äkç ÞçPžå€à‚nÀæU~èBœèB.æ5îOŽè„®èKÎèuìèJéMî¾_žç…>ߘÊšNåœ~çtê–þ>ä£ÞÊHêéÝê¾\é¾è‘.kMæ´¾é¶Þéô:é¾ë¦Þ먾çª^ë—~ëqëj.ì[îæ²ÎÍ¥þì§îã«ä°ÞѮѯ~íh®ì ùé!®çºÎçà>£â¾ÜäÞìæîë`™îliæÞŽâÛ®ÔêžíØ}înÀì~îìx^íp>ï°'ïÈÎêúΑÀžéþžêïåøn{ÏëÉîî›ð¤¾ðÆÞðëjñ®>íÿNìÖ^ðØ.ð†~ðÌñ³æ"ÿí¿°ðÌ?ì_ìå~ìoð-Ñ/?ò+Oï&¿ü>è*ó<^ïs~ëÞïíNóìnó2óL¯ôNOíþ ð=?ðI/ôÐþó+¹óZ åŸÚFßíW_ò9/¯^/çaÚcïñ _õOòVörë\“(/í_ówyÏínŸñp¿ñiõk_Ùm¿÷Mß÷•Xø~œõ”¾ô!Oô_óT?ó“ÿôöìøsoùùVÏù†_÷Ú~÷núøøC¯ù,õZ?î®O×°?õ£õ™ù0ùÁ.ù¥/ûjúùîùo ú”)úoOúq_ö¡ü /üÏÏúÑûnû¶oüRÐßãvã«ý÷ÎýQ€2 ‡üU)þñ.ýàpáð¿³þ·êÿûØO½ìßñô¯ú.þýËŸÿ@`Z* ™T.™M&PN©ÕiñèÔn¹]ïÉeóV¯Ùm÷û ÍÂéuû} ¥XùV¬JªoPè¯.бÑñ2Rr’²LÎ2Só`/±oñ“/4.Š´Ôh“µÕõ6VösÖÖV/Õo•SP÷Š—nôwÈôö9Yy™ù¬¶š¶“xN•º0øÚ—Ú8<\|œœÓºÝy:›hû´›ø›mØÛ=ý?_úyß_+;ykèųצà¯ÿ6tøÐM?ˆÿf[˜&¡®‹h2¦Ú8dH‘%޼W±Þ¹ˆØ,œÇ2¥I™3iî+YSJþƒ*ÂÜ È§B—8‰5úêæQf:…ò| ¯)P¨‡*µz«¹¬üÖµtJ0(Õ¯j:’ú¸mZµ«®uÅT¬TvíÆb ë±­[½{³&åË .ÞºïšÍ«nªà¿‹cõÛ¸R`ÃÏ=[¦ì§Ë9wÖ÷Øó—0ÀÁÉ«Y“X"Yóa3™mC[‘ìлyƒÝ[Ë><°¡‰@|¢Äë®1{YÖM÷ ÛÀ¹w‡õÛ{’Ç“0¼*ìÚ×oŽMy¶ûõðÃ×·™=÷”¨þÀDµ±ÔË>ìä#P.ò»Áµr°‰P"2`B¹þ ž˜î§ê,°½Ä&ƒÅ¯¾Pb0` :ø ƒðP‰µ[ð’$QŒì@RÅ$•ÌÉÝ x1‰gÜB„ίÔRD.—üL‰ “¨Pƒ.à¯G£ê’:a„»¦¯ÁTšê& Þøi¡Ñž{&µ¿d›fˆ·.;kºý&©í»­&ëoWJyÈ¿Éî%ñZâ¸#_œr†WòqÉUæÛðÊ=Oçr<‡zl³ßûóÔÑ ]ÅÌ9gþùt²UŸ=§À]îÂaï ñ_iÿÝ7Û1Ç}rÝÅŽÝtà•?†õ]7žðÒû^žzYš—øÍ¡'ùé«ÿ¾•ë!|^úΟBüô^ô°£ïÞ|°z¿Yýú5ßAòáßý|Ùíÿ_økWö§¹¾îxT #È ýñ}ûKà)(§¼%€¾Càû è¿ ~ð[;˜¼þ™„)$Œ×–Aúm/wå㟠i(¦¢=ð„Þ“ kØC€±¯u.tÚ¹WBú‰Lb¡àÜWÄÆ,MØ“¨ÀêKˆS„œöd8Á*~Q:T^c(A>q†`TãíƒÃzþЈPTãÙX7Fq~C„añæØGÔ1E:@y.´”ÄÄc8WZ?Œ6F£LãhIçåO”Ö”¥Í¤æK?JÏ€ÆT›@ßM“S“ú´£·,ªRmÚR’tš'*;JÕ¥Z‹DUiÓ8Hšz•zL]ŒS{ºÓ¬B•§hUœZ_†U¡j5®z#¥\Ó Ö6ÚU4*Lùº<ºò…­5së]áê×Â&ì°<,E…9Ø©>Vy‘UØdS*X²š³³Ó¬[«×qšu¯¡­cÉY©Ž©õT­êF»–Ң쬙Ìíl+WÛ£¹–«„ijyë9ߦå¶NÀë[[\òv‘bõll»:ÜÓ:wqÇEKrÝÖXÆ6»þ¿ÓîV¸û!ðv›áýÜxû\ËÂÖžrT¯ßØ;5é¦÷³2Eíuç;·ú^¥¼Ò9ïk§Ûß¿ý×kîÍoRuK\4”à9–ð„)Láè8”н¤‚©+ÜýÞ“0¬ñˆI\b¡b˜&VJ€Û4àà^,¨ÀŒi\c߸Ð0Š¢â£°ø‘.~o­ b#vǪäp|ËÚ`þ²r·GÞì‰ zß„¿†q‘å+å)ßpÉ\nò(Ÿ L!{¹©I¦¥•+Šå+¿¾b-šI«f_†ùÃ]†²ƒÉld:#×ÎÈij“ýlæ(šÏÞn ½9è>ëÑeVæ¡=5*”Í•þ]°ló<æ=KºÒ ¾´K) ÎDGºÐ“>u¨E æLÿÓÍm†3‘;=gV[ÚÕ¥î癵çZë÷Ö¸ÞéÝÀf™É¶öŠ}ÎWÿtÖÈ–3°íéeÅÇØvô§SmjPïZ××Nq³÷¹mTCZÕߎh¸Å-“l…Ø46eaí7·»½£¾ê³·:diÿšÁÕV¶Z \aƒ:5}7Qâ½Üï²[§Op‰)^qrÓná8iø¼Ñ{ïMWW²2ÆñÈo¬c}0ãi3wº» îU{»åíå59S>n~ç·/w¹º7*qfÏ|›5¯ÛÊaŽî¢[›åF'/Ç-*tw}ç1ïþ¹Î×Mõ©=šN7ÉÆ±Nh¥Wç¢AøØ'|ád8\áÏ(ÔÁ.õˆ[ýíaªÅé.â“O;à±ÔúH¸îsÅúÝ´nªÈI^ø«=ð_O$âÓ|sæ>çro+ä•ËtæY^ {_Æhtt-`?@€ºëø‡Ãý©]8µ¹­xJ }¥šW†«ŠC&”ÀØ€Òc‰òÝU}ë‘~ô'½ø™€=HeŒñÈÊšjBx{d+øæÍ¾€Qýd³þÜɃ÷ñÎé†ÿvw¿Êž”Àüg 0€0ð,à£~òøÿ»ì ÔkZ{]ü ÿ¾/ïüac¡àÈŽþìÌšïàúÎd9˜€C”`<~ï‘LïØ<ÎÿôèÎüV¯Ëàðøô¡ߢîVñ*Oý¬ÂE`DF˜ @ >˜¥Ø4Þ Íß®,‰ï£Îõ¾ Ãoñá7A ÏðLnðžF`J’ jðsðþ$oÿ¼0ñÐe ü/ ðeíPŽÇPù0Zp"$„B&p ÀSà—€Q‘ ‘&à8$Ì6€ÑÑ$L5&À/Ñq1;Ñ'1Â*GQ5QÂÀSñAñ9D‘GÑ#lUþ‘Y\ñ/1Ÿci1mÀUÑq1 qA ‡1q™q)‘ën# ¡±'챑‹,ñ31e‘!ÑË1Cñý–ÀìÜþ”€æ/ êZ’@5¦Q÷‘ûÑÿ R ’ Ò !R!’!Ò! ÑB.% ¤ ¨þ¬ H`œ£ ?$CR$G’$KÒ$O%SR%W’%[Ò%_&cR&g’&kRÂ>`\cI ^%V`O ÷t÷ ñ )ÿ†ó`Y’ÀYîôJcô’’*«Ò*¯+³R+Å`)þ=OIppÄ^°&J „$à5xÒJ°Å>ÈÒ,ÑÒ)GŒëÃ`Z€¬xR€-†ö~2I<@F<òð>D@À8.@ ªdô8 ÷t2<“1À1“`Ošã9x%<2à8à| B  $s(“](2S1„0u0IH3• àÂF " Ap33‘Àø ïÃ5K`1‰“7‘ J€3_Ø/ ÜO,‹mSE„ µ°"çÒ>´Ó)SIäϲлaìPÓDE³4Ró(!›¥Z` À5$ ¯¤>/$þ ðs6‹… £d['nD 0P>s ® íQ?- 2< FL#A?3ŽCëÑþðÅ@¯pIH`ÌEæóD‘@C‘ó,µÀý ”A$À>M FM þ\ÊäÙL F´>æ³@ï3?qt8›  A|ô +„G£%JÙE:‘ídO˜4<æÓ<•€<7tJ—`@ä8 ¸3S €L6MÀ<ìFe´B§t7{ó7ƒN—`GïãMáR7›“>QñÅU`… “Ä$€> B'EVS9€8 : ³:`2ïCSÀ:µF 4= ð“Níþ£þÀ0áTEµ5&YL;!JsC :k¢D,R4Ô²þ²EHlH   X @ê44 ÄzY•u+·•[»Õ[¿\ÃU\Ç•\ËÕ\Ï]ÓU]ו]ÛÕ]ß^ãõ!$³Xf¡>%’ . äÕ_‘7eF X»Âˆ”Jà_v‚”+Í€ZÙ4 ª”a+Öœ…5ÀG“/EÕ4¼ï Z$@9îqD65ç 8UôL àJsµ>1à µê“=}4 Z@'GŒE-–h)A”@ Þ4 ` `÷SM5O£Ì#EþëïBUdy¤8 àCIO8 <@Iϳ>`pEbDV|Ô. W@aÿ(òRi‹Vo%¡6‘`c‘@G„RÎÓg)SBk%Fγ<­…8m (¥_2°¥>½3_ý¶AM “€n÷Ös#¡oM€i‘ "ðq¡¤“ÀGkE9² :v³qPd·9ê:ÓöÞô/©O';÷s—NôoM “"ê¶Cí¯þ®•?ãÏrí³f}”2•ƒ-}x·—3rG×ðR `hÅ7 tר¯(Lr—`v“€0‘CJ¨WVÞ”2UZ´—{÷— BwxÍ÷q‡¶êp“àpM`~› 8Ý"wVìoz“@g1÷~S4uùׂÓàS†·x7«–®VC èöF6 2óK3EÈÖlm­Pß6®Ø`}=ó‚{ B|;Øe‡HEV" þÞQ-õð÷VØeaVfi6sé3wÀ†¡%DˆØ‡»¸Òt ÀØ‹Çx `÷8@jõµ ^–ŒÛX"@G˜R»@ŒÝØŽïóX÷˜ûØÿY™ Ù‘Y‘™‘Ù‘’#Y’'™’+Ù’/“3Y“7Ùs‚!ùd, X… 1"""+++444<<<Mci|CCCLLLTTT]]]bbblllttt~~~ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@”pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›´H€ Dgþ„!>{î”´R‰¡i à盢89v€CààÑ©6:êjQ²D›²¡@&”hBõÈ$’@=3 S§fö¢õb‰ª2™`€ P0RB«b@'H̬VHfB?ɼ9 ã&L8 €&uÜ-m„0™¾Ñ %b»0Å[U²¡é "Ç‹nh7åÎ}((0‚ˆ‡Ä!–Ä.Rí"Îùú ­:yß5!}8’Æ+Ñ D†Ë[B<Ð: h"°•'à&LFÄKgR>U€À[S$ì·Tþ*8DòéA†Z5P  À"¡SÕQœ|Hl· wz55ˆUXD f€É 5"Zà€:V`ˆæ¡ ‚†Ž•&"‰+¾ [Ž™ ”ÒQ%fœE€åK¢÷ÐL€Â}îñœðdW›n¶§Dÿ(Ö Á4VšI0A08‚peYÐ @‰iÉYLPé‚òÅ)ˆ ÀJAkß !OvÕ§DkGt—+S¯~êŒ964PäQ tj+b-¢À¨²ˆBxÅph¢l¦S´°‚ @µþ×:Z†À¨d@AuÐJknµj.t§ÒQÀ¬˜€±oÇù™ŠBΡ“"ø€!L÷ß L7©ªEh[T»Eç"¼õŸN ^¥!7È A_ 1UÎG4·ÖœUòÊK¼‰ +ÔÉ(œ²Ç…°U ÐÌÈBÌéÆ{Öñ¥@P@Å\<óª( ¸5 #ìµÇB„wöÇ äÛë‰ßŒ`€'Œà3º ‚ÿ*ñÿyñŒ¥)D¢ÇE™C´9ÖCh»Ûƒ˜BMÄ­˜ë4@‘en޶ 2EãQ @þÝG0‹/Œñ\£˜¼S@^H%ú¤£`yœ#ÀÍ( €–cÎ5ØB0*9 ”o«¼ÎC9š÷äm:1ß½Ý ¥ì¦ÞÒó«Áwe'õú((¬„œ€?êE°6AÑ @Çw€ÐæʃZç Luf\EàÓŽ²¶ÜˆsÜkUÙ€„ŒyC°ãR`36êŽ\Ô¬ g¬ž “£³ ¡cB`Í•ÿÅ+€ gl(¢@‡EØŠ™BD } 9Aú¥«Ÿ%1$‚åÄb*"noIðÀò§„îôNLÙéÿ⣺ZtF*œþ0Å«!8'c«k”ê(„.„BðYœFg5ôNO)dÝ p>¦M-j,BÇ€'Ÿ)’’: ÌxF_U—,5IJ'‚ŠA ïÎH?VPŒ!Ò*[ûVÊÉà_ÿ Ë!ăòɘ‡„ ¶7:S´ÓÓa&‘zCðcçY¿ öêvEHdó~—£d A‚0äæh%K/ÈQ¢&YÓ˸EɈ¿$£$˜'¢Ò 'ˆ€"ÀÏÈl*¢Oc”䳟üdMª¸†u —I°^¢>å%O4å3&`4;B‡™ÐI3êNÑÒ››¤æ‹bþ ¿iç”ÜAgðd…:ŠnñB4©)º9hJ”…&5'O§gG¤,±ˆ0½§B¤Ó>°TU æ4¼"Ø ûÏ^I>±É°iz’ÚØ<Ãùœà-›9AÕd6²c.~)ë¨öL ø`cUƒfQª-#H‡:ÖÁÎK{¥´™­Ïi4e[ך–(¡¥ŽFlÛÆÊºD6à«'Гˆ$yʶ%‡cÊC J?©T†\u6D(Á]+× áªFÁh³ÕvJ5@V 0„tI`]}½c~Ë®œj²­äš€¹DRÑwŸÀek”6åÆ+¶&5«iM@w¬EÖfS`-¨\ þËF-«YE"mõÌ›-éŠf*¸Õ-\JiNã™×‘¢QÇ p´êÔ3©¥Å"À¥ &@…°9Ä)ò;&€Š`à$8ø³5‚k„@`¾4 'A\ª Ç2íH+>:êcmÆ% ˜¸”Òd5QÖ!É….æžwɇ¡ !àzø‘\ÆY&•ØsQò0ˆELߤj€YNZo²B2©7ÀYx61€Á'0À:€_¡¤¯{hg7jæ78¬@(BqŒü¦Î¹sÕ¶[ç7§g¼µÙGò%L`üM¸Zh7˜ MäçÄàþ¬˜#XQÑŠ8\é7€ŠÏÃYE ]êV¿D:RM¢h2ÆjWÛ:%÷!õ‚6}®"˜ !°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸­‚8L÷É£r-ú|,Œ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎðy·/÷&¡pà4®ëÀ¸7Îñ޹ÈÅ ò‘›üäØ.9ÊWÎòfOEOÒ ‚‚`{/­jîÀxs”Q<”¦FЧ1ôhˆàçÓðÙo>„ØÁGGu5Š ªþCÃêΈz<´Ž ¬;ÃëÍû2¸þ²[CìË@»2ÔŽ ³·ÃíÔxx5ä÷lÀ}w¿µÞ‘÷tô}ï€ïÃßÏ1øÀþ…/GâÏø8,~o¼ä×ùpT~ò˜/Ã忱ùÌ{þ ïFè?Oú,Œ~§/½ê§z»#}õ°'Cë±1ûØÛ µ·FîoÏ{!ìž¿ï½íƒ/ â õƇFòOúågýõÌ~œß êK_òÖ;ô¯Ï}%d_ßï>àÃßöí‹ÿü>—:;È~W³ßïo¥ãO úËÌöFþï¯ÔýÃÿüEè €ùR€¼€€è ¨þ ¸€8ñ€¸ Xh X2‘´Àø( !ø,1‚°`‚$˜(è +˜‚&Ñ‚¬ƒ.82¨ 58ƒ qƒ¨ ƒ8Ø)…Bù =9”¥p”²W”HÙJ9OÙ”OÈ”ÕG•R™QYy•Yh•Ú‡“þ\É[ z^–1–^€–f‰†e™ j¹–mØ–ç—(ñ–[`—ty‡ry x™—|¸—ð˜~©‡‚Y…9˜x˜ú§˜ˆIˆŒI '0ÑÒ2Hobrn}Ù˜‰ –$Ð)–£nC A Á8E™šyx‰@ ùM£ˆš©Yxiô#¯ù˜±éˆ`y ´)qD AwAaOå{¸™›¸›Æ@›­™6ð!°*2œ°iœ®Xœ¤ œN@˜všÖIÚˆ ØéYÁÈéaØ¢pi¾™ãIG‡Ÿ@wæ™%ŸBt áÉÚÙDÓYŸÆˆžŸð Aàeþ_›Ñà  ÑIžŠoÉIb2·²Ë‚öUÄYž:ÿÉz¢÷X¢ˆ¢&º[§¢+ª‘‰è¢/:’2Ú5:£2 ¢Ü0¢8Ê7*‚?ڣʤ'H¤BZ•:Ê¢Gê”Fº’Iº¤‰ù¤0 ¥éÓ¤­À£Tê‘RJ{Vš¥Å€¥P¦^Ê“]º b:¦šW¦6¨¦hú gÚoÚ¦Zɦ?¹¥rzœ-j§w*Œ1ª§{ú}ú§kH§§§‚z—„ „‰z¨6ê§9ʨp¸¨E(©Z¤Ž: †Z©V©IÀ©šJ¢—z“Ÿºžz¥:ªOpªŠªø ªîI©¬š ®þš~±z³ú¡µ¥yš«õp«R«¼:©¡”좨ƺ«Èš¬e¬SY¬ÌŠÊ­º9­ÔЧËz­ê૾š¬Üê¬Úê…àJ…ã®—ð­Ðj®|Y®]™®êú¥ìº Ýj¬èú®¤¯âê®öº˜úú•ûjõú¯½ˆ¯™0¯Á°KŽ;† ›°›Ù°lÙ¯» ;±'*±n ±û‰—»±ù±ë*² ; [²SêË«'‹²4J²ð ³.ë¤Ö:³-)³úر6»-»³ÇÚ¬8ë³uZ³BK¬D[´F©³r¨´HK=Û´Ìð´P+ŽA +›«R;µs™­þZµL«—UÛµä¶nz9Ø&gbƒ_ûG'pd›¶Õù¶½ptpv{·x›·z{°¶p¦~ÛGgP¸†{¸ˆ›¸pû·pÚ¸‹0¸Š;¹ŠË¸rë¸z¹ „K¹ž[¸–‹¹–Ú¢û¹”º¢K³1Zº¦[¹›º¦úº˺­‹¸¨ »f*»‹È¹µ;¹·‹»²ª»ªÉ»½›¸¿ ¼…*¼•H¼Å{¸Ç‹¼I©¼²É¼Í ºÒ »ßJ»Íû¼Ð; Ù[½Î{½©û½àk½šÛ½Ž'¾‚ ¹å»¸ê‹¹ä[¾Ü‹¾Ï '°Ðyá •Ô[½óK¿c &Àþ,ðs ;iª½ÅÀÜ®À"‡ósÀn ܾî{¾¼ïû0?€ì+¿ü·ûxfMós0ÂdÊÀ½ëÀ\°'ܰ*?—¼”0\»2<ÃçZÃL €@#  kbPÂàûÃ@\ ûx>sÀÌvWÀÄ,Äb[ ðô¹Äÿ»½ZܵñÛÄe¬µgœÅüÄdÚÆ†Ð}"J±cdÙíëÄn¼´p\ YQŽä¿xlºz¼Ç`›œPkÈ!™¼œ(àt fXLÆ}¬ÈsšÉŠÐ;,1³Ã(Ë™fklÕ6»?w¿0œ†œÏ`Ö©jÕðHGy&Æ?m¼l=³)œ½–6.ljǃí²û8¯QBÃÏóÂv×| 0 ÖÕm»²û(¯¡VTÑ…uÇ—Ù‹ü OeçvÁ]Ú%»´µmÙ·}×°½û¸ƒiV׿Ül;Ø$ðhAý د­ÜXÚvÂ#A&ÝÉMÝÕ ÜÐÜ,ÚÓÍÝà, ÷[ÜpÜ®½Ýä]Þ¿ær€¶`°ØˆŒÛ «Û0tíÛ&ìÝíMþ«¿PÄo`߸Œß+ÕN-N“môà þÕbì¶.Ü®žÌB9¸{Ûá{Û·žàŽ’^½ÖÅzt0ÞÎÔ!þ“£­Ñbœâì½â,Nº*žÖ ãÿMã¸7ââ=ãi™ã7Îã¿ÚâÉûâ†;Ðy­ãhläá&ãCÎãPÎØ'.ä@NäEnãYÎ(>å4^å÷}åLÎÆZ¾ªÍŠäæ+å]Nå>îßMÎæ;~æžæ`nzX>çt¾å«{çXðåmæo®Ø%nårç{Îçvèx^æSèb~àdîç>è¶­çdéè é–ÎÔ>Ôš.áœîäÑ«æ|èf>ê\þŽéAêQ®ê}Îèžçˆ¾ç‘®á“.ë•Nê“jêJî€ÎêZ~맻ᴞê¶ÞéÈ-ì^~ìžì¼î½…>æ¨þìtNì+ëÌîæÑ®„Ó.éÕ¾éо굞éŸNÞØÔá.êãëÛ®Á^îgžî‚­íò>ìÊ®ÝïÞèçÎÝôNÚöŽìמïsêëÆîê†닾ﳎðÔ®ð*ûí¸¾î¯Þî ïÍîðàñŽhðoíóNðõ-ñÅþñâ>ðÝ ^òŸðñßòÿòßï$ªñÏñëGòÙ.óOó;ósŠó,¯ó(ô#Oô=oôÛÊóê>ô6¯ÜÿãþP¯ëí=õI~ðQÜX¿æUÏð»Nîßê[Û]ê_ñÜ.ö ãJÿô@ôe¿äs×gÿëûöõžèŠóuŸ÷OÕw¯õVî"¿Þ`êÕƒoòìŽòlòd_øþ~øAŽôæNùÔÝø>Ÿóqßô˜?ù‰oø)?•¡ïö‹/ÔœŸöcï¥ÿ +¿ôIŸú½ú³¯ùRoù8~úOûï¡m>±¶Ÿù£Õ÷úp9ü¢¯öðιþüw âû¬ûÍÎûˆª÷ßùEï³Êú¸/ÖØOõÜOýˆjýÍþYïø¿³Ýßû߸èïõãüûpOüÌÏïïÿ®íþý¾»@pÁ ‰EãsˆPMç•N©Uë›Õn¹]ïÉeóvŠ–j÷¿²-H;RÉ|³…wÿ0Žïï/Pî1Qq‘±Ññr‹M/²ÒRŽÐÏÐmPÓŽ ïó(ô5Uu•µÕUnòUv6³Ô´moÔö–R”twv˜¸Øø™,6™y±8¸7Í:IØ·:ºy›»Ûû;q||ìúúüzZ7<^~ž>Y¼ŸÊÞLŸ]:wÕúå3xaÂvÒâÛ·« ²ëžù1`C9vlrÏc¼‡¶"–›°¢¿“S†tùæ61»,URÌEþ’UþBIhP¡¯fEfóÎ0:oò,Ã4©S£S©VØÒªš0àaJcÉš€‚T“R0PÓJ5é“eV¹sév)Z÷LBDÐA D„!âÄYu޹͹r'Ö·Ù¬IÆ{³Ñ»™Ã|øëÄÁ€ÀRÑRû…­iÇK!7µÌvlŽ›es‰ à‰ØQÄb-]hõÚÖQ_ ‡¹vr峃/Ÿ‚àÀ“6D<€€#¢üÞÔÜKjàÅQoKÞyzõGÁ¯oRÀÁ“4DAáCˆ ]¹?ñ~ç´ðÌS =»Ͻl†¶Ÿ€O>ú¬ !€cŒ þö¸ï»7<°CEÑ•G„N:ª³ÂÜü[Œ² <Î5A@PCuÜq!¥ˆ €ÜkQ182 ‰³‘Æ%siò<¥œR¸ÒrTÏ3°šh@´ +¼0Æ-³àG%¹3©|ÎîÜtN/¾ü ¿JøR@Ѐ?2SS 4Û$ôL6ñ˜3ÎF©4qÄ& €¼DöDa`HSÉŸž,Q, ]4UG]õÒW­øïP&QµuPYuÝu F_¥•U\3luVEyáÙ_}uØcOÍõÙa“6ÎX©ýFh;1Ö?„’ÀkÅ…uþYG›í¶\)Vuv[pÞ­½öÜ"¼]ÓÝ£¥(Þ~œ—Úz‰¸·PníMWÎ|Óô—áõžV`mö5µÝ[¾X¹‡“¸2aù8.ŒEŽMcd9&8Q…k¹‘]Ƭd^OFX1•ƒe¹Æ—u¦+æ]g&¶ŠuÑZƒ¦yç¤ééYן=¦m•žZ(¦eug'+–šê®a²ZÙRCÞZÚ¬£ôí—Àf6Û²Éþøm¨Óž[£µÍmî¨ÝÖ;oºýFÈîF±Ž{l¾åþñz¯ïÃ-2ZbÂ[Nœò¥‘~´ñÂÛ±™]Ã5¯ôo‡spÏ'7=çÐU÷fô7KßþÜbÉS_vfZÇ\ìÓaçZ ·®x{.ŸòõÞcG]ëà•§ex)‹Ü÷ƒ‰žBhé—¿^–Û‰Ï\wãy‡þxìÅGE{繟}÷½Óï{üö!)ŸÜÜÑ÷^}úÙwgš?|ûï©ùà!àÇ£çð{¬ß˜‹é î|É[ßÿž¹Ž5ƒ6z ãä'Aÿ}| Ìà³AÒEðl²ó`HB–Ç„®Ca¸ÌFC¦ð…9„¡ÎXÁè-†½úaätXÄ5q{ÄáA¨ÀûŠÔÛŸgø®ÚyKŒâå„Dó)‹Lì ŸÈE.pG=”È/xE+šþŽØò"ÿD¸Æ:‘‚q4"u¤ÆÉÔñ-Ô㧘Æ*êˈÌbE>’Èa#ÊTeAJ6Ò}”×!VÃ7Šq~˜,¢&EIÖL²yÕ¢(ÏXÈ>rre‰ì¤,cÉJBΑŠ`üäLjÇ&Ú’„¤t)sG˜æ¿`y3Z:s‘»Lf0] Éfv”+ôe/§‰Áe*ˆ˜åA¥UIÄnR—†Ô¥"³©E2æñœüf‚Â) q>í—ñ”g57¹ÎYF“¼ ¥>8O÷ÔsFÈ d Š?ƒ:ìš¿ód@ߙφ¶ï¡êAè·*ÉŽ^{MÏFñõÑbþ¤!åg)#j½‰þ³ŒLi&W:Ì–®ò¥µŒ©4g:>‘:‡¤»gN¡ÙSš¦ó•þÔ©@µyL”x?]NPSfRqZªª“jÆnjN¦ºs›ͪò¶šªVr¨ÏÄæX¯WÖÚœu–L¥\‘ÊÖ‹¹U6p-VZ S»’µ¦ÌTjQ¿*Ó°6õ¯«Ã+ÉºÚÆ¾.µ¢ÜL¬V ÎÆ^r¯XMèS'›¸ÅÂF¯A£+>%ÛÙÊ}–3¡-_wJQÓ†µ™Q­Y[Xž¾ö´•¥çeçZÛÈŠ·žÕíAyKÎÑu­ÁÍm]û©YŽröªÐU®×b ³â’¸ …çtçVÝËÌV]þÇU«DÝp‚ܽéU¯zÃ]Ë2—¥ƒMîaÁêTAJ„,ùÕï~ù _÷2Ç¿‚unI¥»ÙûšÄPð‚Ü`_ÀÃý/à$¬Ñë"—¼%ìo;aÆزòÍpkýÊáúÚ±ÀæY…GzáñºôÅ8±WUüVÕÅž¯}ª]‹Öغ Þ­ˆa¬ãÛÖµ<Þ.ñâÝ&ç˜Ä¥¯a•üc&×ÅÉX†2’K<åÛ¢øÀWÖ²‰Kd¹ÈQÞ°—“,æ'“¢f¦1›»\åÒz4ÅnÖ̧ºeSÌ=Æs˜õœ•,¯XÎŽUóŽ½ä“ºÐU9ô\À›° õÒUþÍs¤2i¹Tºf™F«¨ãê[NSšÏ\M4fEkj;gwЂ>õT»Ø°>ö¦Õ `b;Þ»ž7¤£Ëïz»ÄÚTi¶ÓmizúàÿVHÀio^G»ßÓŽ8·®ìpçÕáû–x·žÙŽWÜ ß³»±«ïmCœãþùFD>”c Þ×öÄQžó®ç9G H[þk’c8Í\Îu¶å½bþ釭ÒA›ñ“Ï<å7þ°Ô™½˜_ýÁf7`·“­tå+T0Ám€„÷ç3V´Ð×ó¢3,}¢›œæP÷‚ùóM샰_†Ô „œ` A!xBp)l…Ÿü‚®ø þ4Žâ/ø´ þŸ®èðaQAñoñ&¯è¯ æ£>žàòoÿúÏ÷øDPøò棎5Íðí ïý˜õbPþâj;E°Oû `,Ä ¼ øo,@‡‹Ð- þ½@ šÐ Ÿ £Ð A½Ä¢Ž 0 ;` ¥Ð ¿ ÏË ³ ‡p Ñ ¾P ¡0 c Ë Ïð¼˜p ë° Eà á åð0è°ÕðEàþp ï0õÐùPü¥0±Ù0½ 1§0½Ò°£ð3Q«€BQG‘K‘@ ç%± WÑÅÐöàD‚þÄ j! v¯÷„${уQ‡‘‹Ñ“Q—‘›ÑŸ£Q§ñ_ÏG*ïK 2O6/ L  CÇ‘ËÑÏÓQבÛÑßãQç‘ëÑïó1C ÌNêî¤ ô¤ ònïú.noåRg$…R,Å ² ÆŽRÌŽ!/#3R#7’#kLRÀÎÿ¤„ÿô«BRô(à,àS`TDä$ %Ÿ Sò+E  @ÅzAX²B^²_êN ©èCC€G„ ü"ž€BÌÎôΤ)Ÿ¢2"þ 0£?ä€/<àþ® ¦ƒ*­²_°.Ï(A0Nì+›à ½J &@Gè’&iqG®NÀ)›ð² („/ãeöj1§„ÿBrJü² =ÐòtD(Ó0“Jro0-óK0^ :°:â’R ÒG4“r° & –RA4S@@“D:ÐB^sEbs6ãQ@eòƒ ¾G432àxoSDÄ67@@à>¼‚6ÅÒ+Q :€:ãŤï8å 4[s+ <›`ñt?7²Ï>”19³öXS AD3;Pÿ,ï&å³@¥À7I$F´ @ó-Ct\Ü24\ÅÔs2Qô.Û‹BT23Q”>TANGŸ F3~^ô‚R.§(Àôc'“D>Àã<Àc*A *€7DJ©ÔJO `@àdÓ6t9xÏ’òö¦ò>€-ûe+(%ìÞDÎ27 $¢ò+vñ'[R(äOµLàº" €Ôt9€¿Ö(]²#/S3US7•S;ÕS?TCUTG•TKÕTOUSUUW•U1²*þÕà^Ó¥ > Usuì¯ME út0E K¥ "@Wõô“ 5ñ–u  A‘UZu,?`@NuõB@…6 ª³Â5zÒ ªuX’óÀBà,àG_S2`'?à59  ( Fà@%üQ¿`ZH ó4@FàD Àø.So:¾âBcy/¤4\_²Z ;Ï`ÊTàCçcÀOH`>ð? þ¤X?"]Áaw6ŽÒK²µ ºbSóCö*™sOôòC3T óî¾pu `T^“Eiµ_þïfÑ n–g¿ö |¶ ö=®¯ €ø¸Ï ðsO¸11ã.–Ãõã5Ó@ñäD…2óüÑkÁpÏ àgeõ=¤®ŒÕ 2oOxoRo àjµÏ^¥r0²ö*ã%ÿ6p?W Äȶõ À`KWúN´mûO½¦÷8ó(ã<óvlà*ÅbS<t{· DhU÷tS×h ÒSöär¥ Z'7j#²÷’×@ùð3wwQm}{·`p b÷ú.Ö2;6…÷$ÀO<`¸²1-ÏdQVe…Bóîüusà%`> a³—­@tI·8Q׌=ÃU(€÷ï€ro}Ï6^çµ^µ°vtq× cSF \ uû׃ՠXt <óƒKX$€ï<àž– àÕ„_¸&€Q H†o‡sX‡w˜‡{؇ˆƒXˆ‡˜ˆ‹Øˆ‰“X‰—˜‰›Ø‰ŸŠ£XЧ˜Š«ØŠ¯‹³X‹·˜‹!!ùd, X… 1"""+++444<<<MiCCCLLLTTT]]]bbbkkkttt}}}ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›´Dx D'þÏžŧ%£i ‚ÔMSœ ˜JU“(:iS¤#vJyŠ„ƒˆ0¢ÉÔ#ˆH¶̀LÕžaÛêÄ.\ Ã4ˆ°€¯B  ™h "êšh sá% E§‰°™A„6s`r×H^ÕFN‹ùø§™´¾7L±`(•3 aíÛÃÞdÔ(Iu?ñuì‡%µ‹HÅ]ä:ÞÂјOOÜ"z'"¶) Àt"¢wÑ`@€`6DüA ïå'A,ÅÞYÑùõ!à>‰Ð€Vþ4¥ xǶ“†Z-À øwÀ…HèwEd`xàyîB²8Äbq†Á}°!gÄXà¾ÇZ¨šNÀ³A :˜USm âVd{5¨|·„ŒQ™B@çY 8 rGh°™4ÀªA0(p@ƒCäwˆ½±7&P °€Œ•¥ˆbÁ xÀ£D¼— („ (l†£ÙÝY*t±Ùã%qjª˜Ž%¦¬1f ™V¥ÀZŸ†«¨©‚ !c€ J¨˦cA{h¢‹÷¡þ(&wÝ6 .®h:$ÕTcà&¬9F„¡|a' çÉ¡“ªå×@ÚaVÚ5:*ƒ!^áO:Á °z_Æ F*„£ :("úš€Áfq ±-‘±¦|ÄT‹-ƘcÍ ”Æ{lÂÿ ÈÊM,D0ÀðeB<±¤ñ!,ñÂ&¤¥´ ù9=´eBb|¶fL*×MÇÛPBlãqkPDv6m+ €ÁÚ™C:]ÜÃŽ<õ×g á¤`MY,ôu5‘YW ]ã&˜Ên†y ¡Y®ËÁ¬˜¬ËÊ—èD nBþåC„°¸N!:nÕì$þõØÕJ¸†‹˜» ˆ !»¿‹¯g<ƒ»« k^Ó€ÝDà]D~{#Á%|y±I`¾Îƒ`ÂàVÂâ$áùƒÕš@éDø¹qæÕKÿÔÂZÔpÀtðœ`rf>Ñ„G~7zþ~¤ºØìåV“L –½ÌçvÕ‚XÎǘô‰H€Bààú"8ú˜Œ0$‚XÆ'½…è$zFÈWøµ=ý0Ÿ4Óº©|G*ãcÃ8G-ŒÕr>D›˦¹­9x¶ºŸxL&'`Ï5ëÜô@zÐ:QdO¸Á3²¥2Lâ þ«%•±´î;lQ¡fÆ'à '5ªáCÞ€$ø)fB˜S»Ç70€†Dˆ $…`Á†:!‚|¸¡':q‚.Ú¤räLa“£‚Ku¼!d…·BÊè¸\Ð`«Â!×gK ƒÃã oiŠl…“ÌÜpTXÉYú±y,¤ ’Ÿ¶hqÅ¬ÏøþÕ„lL‚ð’@;†…ÈvÁÑÉè´Àn±Â }c»;ánÎ˥旹AFÖL-;ÇÂ#¤E˜‹•˜âÖ¯çqr™\ž99Îk3¢½#t*E@Nó !Àœ4“2Ì-‘èkþÉ0FnO`Öê_BÀ™“–ÀMDƒZ1c"ú@4%8Ú3Si?QšÚ|(4«U­8L¨Ç#(‘×JWZæ¤ðlDÌ¥©ýÃUÒj¥0ÐÑi xzLš€˜7•ÔYŸæ°ÊôTŸ©DéG Â3 ŠGM#Òm0×@L§&àÀ`W* 8n2Î#l`,®ä2„hA(.p(û‰yìǬoi*ªy%-“pZ%æÕ@Å”Àš¦½&6Ø”@)ž‚öU p-6U,D!‹H‚gµ¨uá²¾Õ,g3g+aòÎX¡} æþÆp'º{Õ‚ÞòŸ¨ $p€V,44 @qY€ïJ¬øg]m«JI±) !p€ä:m‰I:ààðSrí„âv4_:ͳþ" [¼*e n gèbâÞÊgcÁf`!Ì}¯>)»¨¥%5‰£ÃqË‚ Ð`¹šÀ»*ó1 ®„¡Í†é¶HN |ˆe 0€ xàh÷2‰x÷LW”y T’òôÔÔ©Z ï5AhS2f¸­…G®šá°€€¡ÏDXŒ?rbD@ŸÊ‹L÷ì†%ÃÒq(dš,CÂOŽ|Ù?Wʣݮþ8·0ˆ„Žž7Mj˜d§¡D@pD]êV›iH@¤8là ȵ®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎv³?°=Iƒ´œ““Ÿ]„—ñÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀÕ½_h6“óœ£fÝ:жÄ'Nq_# âÏx¶/®ñŽ{üÙÿ¸ÈGNì—…+c®?€kåµá]îN~Ô…ccÑÔÀù4tŽy•iò ßÁçÙày4” ¦;éð€ú5œî ªþ7Ãê˺;´^ ¬/ÃëÊ;2¸Î²O£àÔ@ûÙ³avu´ÝÕpÄÛÑ1÷¸Ûu7GÞïÎw;ìï»àáxq~ðˆWÃáÁ±øÄ;ž ÷FäOy/Lž—¯¼æ±ymt~ó —Âç±1úЛž ¥·FêOÏz#¬ž¯o½ìc/ ÚËþô¶‡FîoúÝ?Ýæ¼¾|ß â ßñÆÏ:ðÏü'$_Ïo>ߣ?öåKÿúH þ1´ýVs¿ßïþ¦Ã? ò‹_ÍæFúÏ¿×õÿÂýì$ü{1ÿø«­þ»À¿ýÛ£ÿ\ôÿPñ· €hX X€3‘€³Àþ€  ø€-1¯`¨Ø ˜'Ñ«‚H"˜ %8‚!q‚§ ‚(è,X /Ø‚ƒ£@ƒ2ˆ6 9xƒ±ƒŸàƒ<(@Ø C„Q„›€„FÈJ˜ M¸„ ñ„— …PhTX WX…‘…“À…Z^ aø…ý0†`†d¨hØk˜†÷І‹‡nHr˜u8‡ñp‡‡ ‡x¸uÖWv؇vLJ…@ˆ‚èvˆˆ?wˆqgˆƒàˆŒ¨w‰˜‰€7‰t‡‰–¸g•¸‰Œ§‰’¸ˆ 8~¢XŸXŠÝŠ}ÀŠªèy§x‰¤øŠRæŠ{þ`‹´¨z±8¸˜‹°·‹†Œ¾H½ˆÅ8Œº'Œ¡8‹ÈXCÇèwÊØŒ’GÒ¸€ÖˆyÙx¸°ÈŒÜÈÞÈvãŽ+ñŒu€Žæh ê8í¸ŽåWŽ×ðŽð¨~ò¨‹àXÓ˜£¨÷wÕ@þHù‹ü81!o é9 ÙøµG‘©Éy‘ h‘ÉxyŽù ’°P’i€’&É#ù{ ¹’Ø’Å'“0y„4©|/Y“$x“ÐÇ“:©*yAù“¦0”e`”DYƒ>Y}9™””c•NùƒK¹}U9•!•a •X™„W ~þ_Ù•þÀ•_@–b9…aMy–8˜–Â`–lI pÉs—bè–ö¸–vIu©}¹—lˆ—À𗀇‚9 %@‘påÖ:ÞF˜…i‡‡) qnºäE €k 0>™{8™¤ð>˜)~ŽŸ š…(š£@VÔ˜¨Éšª©ˆØàšŒ™?ƒÂ* pRBš³)w² ®Yš\#ðà¼é›¿é‰Á à KÌÙœ~ЗÑé›UÕix÷œŸ°d·™ÛIƒ ž  vÞ™ञ&t ÙÉñAàÙžËh À~ÒÀ‡ÃB €à œÜyþŸøYz9 ó&u7¢q,õ1Uâz º ÚØ «˜¡úyè¡Jz"* Ý9¢tp¢ÎW¢(j!Ê¡-:–,z’3£ù¢6*ê;š£Y£®Ð£>ªx@Ê’0:¤ø ¤(V¤HÊ”8Ú¤ ¡¤J ¥P•L‚WZ¥jù¤ZzTš}YÚ¥ƒ¦¨ð¥bºdº‚iz¦ù·¦0è¦lŠ fŠ¡G§í0§ Z§vºxjžpº§´Ð§A£§€š‰„:Z¨˜¨:ȨФŽJ•‡ú¨¼©ž ¨”Ê£–J„›š©j:©Ù©žú¦ ê¢£š˜Zs¥zªÛª©ÊªG'ªšþ𪰚§\Z«óàª²Š«œºª髼jªï@«Áª«À¬¿z«Èê‡ÇJ’»º¬˜`¬ÐuÏŠ–Í:­Ì ­Øz§Õj Äʫں­|Ú­XH®âz†æÚ…éz®Œ®ìzîú®¨¸®wy­òJ ñz¯Áh¯ÙJ¯úºšüŠ“ÿ:¯Û“;°¼¯Û¡ë¤  û° J­ +±¶±‹¨ËŽþš±ß¹±` ²K£"»¥#Ûªëߊ«{²ÉJ±.;±Ãš²1›Ž4Û®7[³rв:;“%û–9Û³ ´’ù³BKªÊz´Îj´cÊ´JÛ¨Në +[«<û´Vµ‰µVË þU»µøJ´ˆ0µ°Úµ^ ´ZÛ¦g[¶Öš´j{µlÛ¶!û¶pk²3›¶s+ d{·h+·zû~`švÛ·*û·† ¶¬š·‚;‘«¸‰·0Û¸tˬ¹Üº¸“ ¸|{¹ŒK­çlg¦¹\K¸«‰o– ºDZº„PÐ °ÐUV:0»´[»¶{»@¢+¸ïHšêã,e¥`Æ{¼È›¼Êk°»}ûŽ)¢> §~yP¼Ë›½ÊÛ¼¨kºBé¼Q سp MfÐ1Ø«½ê˼à{·ï˜eÅ´p`¾Gy½ë»¾Ü뽤ðŽé"FêsÕK¼÷«¾ù«þ¿¢ðŽð>! ib€¾¬½lÀ ðŽ%P‘QX)i¿¼¼lÁ—Ú¾Q°° Àž[éÁœ¼!,¡۽&ÊÂ-|¼/ óJ Á5Œ¼7ŒÃѪÃMðaEÜ4ÜÃ? ÄÞ*ÄLðŰYàRe™Ä5¼ÄL,—N$€%L˜•òšEPt4†:<ÜÃÆ«Å[Œ·]\œ¤c(Ĺe]æ<’+i¬ÆlÜÆ°‘åû¶™2ò^[æpA“¾|üÆ[›‘%p_‚LÆChC@n‰¬Æ6ÌÈVûŽ €¼*\Æ0žB ³F'{¬Äšü´ïþèÉŒž@sJ0ŸFj•Q—ŒÉì+Ã~œØ)ÉbjËå§ŠÌʾüËWÌÄ)kD iÆÌ˽¬ÌkË ,wÍà׬͂¦|dãf\C PÎ pÌ÷ÛÇÔlñ¹ ëÉÄ™`È — èÜÂê¼ÎEÛ ðÏÙœÍÍÍL°ŸýùŸ T\æe3‡ÏÒ¼ÏüŒ¹Õð T¡D'_W‚ÆX¬Ï­¬´ºšÏ Ñ °Á@‹ÅÃ!À¼,Ò#ýˆ]Z‘›¬B``°ÊYÓBûŽ>p¦ºÀ›,É.ýÒðŽ06'nÁÒ:ݳïû Èß Òþ\ÔF½ÃQí8³Qú¢8d½X=ÀZ½Õ­ØÕ&P^&p€ÊH\Öé¬Ö1ûŽ 0A5 0h–÷ÔYm×.ûŽ6åmõK×ø+Ø'ûŽ”†}Õ­Ø#ûŽ\0<ÝÒ’í±‡6(»NؼÙË% sc1בÌhz›-¬­Ù®ýÚS*ت 1Ø2i7 ØfmÚûŽë5ŸûפMÁÂ-±Œ- Ô™MÔÍý°ï¸ÀC»ÜÙ{Ö¸ÝÚÝm˜·Í’Ú ÂÕ½°ßmÛá­éMÝã½Þ~úÞXZÞÛë‘%mÉÝÝí½ÈÇÚ1¤ ß¹-þߪ€ÓÝß׋»Ž»º+àÃ}ÞÓÍßN]à à`Êàe Üumà®Þ«®¦ôíÂÙ1îÞîá!â><âPâ~â¶J­*žÉ>á0>¨)nãWìâÈ|ãñãþÛ<žÓŽÞ~¾žØ5ä>¾ß=¾ÂC^à>Žã2®ãåP]äëäDáVã\.åPþå'æYîåL~ãfØKnâS®ª@îæBNæ¾æÁÝæ/>åvžáh.çM~ä‡Mç~Ùâ‚à{®ä}žç®åû›ä¥çO®ç€NÖ…Îy„žæ`>éC­èW^éë}èžè‘¾èqÎéhŠålþæpþ^å˜N——îçj®é£íéÃ÷ê¦^æ²¾Â3¾Æ,Žêw®ê ÎÜÞåoìÛÝ봮߹þÛ»>ÍcÞêu¾ìWÜìÜ­©¾ÎçÅ.íÊìT@âÜþÚÆnÞÃ.æ’ÎèJIíÈíîõ=îgžíæ~ÀŽ.ì¢Nìå^ê£ÞéênèÚŽ¦èîî©ïønï;þíhÍî"ð¿.ð¬ë§nð[ð+®ðØ~ï ë[àíûßOãõNî¤~ñùþðÿéýîêÿþñïnñ3›òÏîð¸ïêòs^òáÝñ¼NñˆÎð-ñP ñ0í2Áó~ì:ê<ï‡4_ð6¯ìCO•K¯ïA¿îþ'ŸñE/î*ð,¯ô>¿¢×¾ó[Ï­QOòSÏïO?Âcïê_ôaˆioõkOïI/ö]oíuÑ8ïì5_ö_õƒþöƒ÷Fì~ÌWßîY¿ðm?®€oø‚õsïöwÛøÒß;]ø–Þø–^ù ïÔÿ-Õš¿Ì‡ÿù/ñŸàªO» žùgÏ©œOúž?ñ‰_ñ5›÷ÕÞ@úŽ?ùâŠûéÎ÷²ïûÛ üG/÷{Ïû þú¡ûµ>ûú#¿Ø£_ëÎ_»?ýpOüØjüµöL/üÖ]ýØ_ú´/ý/õÊïÜäßíæýɯýÏýÓêýèòê/ÿ§ÝþS@þà+þ@`‰Eb2-™Mçú*FëÕ‚TF¹]ïÉeóV¯Ùm÷O·rûŸïN)XÿP oªêÏ/ðn°Ð0I¯Ññ2Rr’²Ò’ŒîRs OñêÐ.ÔJTŽ´´è”³Õõ6VvV/“öö–OÕˆ.uW¨÷íX÷9Yy™ù̶R±“:Ëz”*;x;:\|œ¼¼Ü<=m:Û¸x× ^U^ý?_?ùy¹€8DÙaBNØQ³§†^©‡ëºy›ècFç ã8k€ ØE >€Q‚¡'oÚê ªØÎã°þ›s~ôùhP)=…^ê`’ ƒ(‰šhX¬éšˆ .:Û µfQ­[¹Þë×ÕÒM~xr0kÌO8ÓêÄÆ³­›©Šª‚µ{說y!8Ð$€ OR  Aˆ¡ká º/*ÄÇõ"óµ|sǸ™í`ÐdD O<Ÿ¿ ü¶cr{ê Gœru/”qš÷îŸ+ÿ<œË Íü@x!ß[rÎA_Ñù$þê´õ^zsY¿×ó„ÝqòN§=uÛqþÝñä½v¡ƒWžøæg1>Ùº'¿o·lvûV ù彦ÞóìÅ×3vºã4èëóþ]ëñÝ׫|À¥W=ýh×WÿýüÙžÍî;Gÿ~öÓß;Ñ;ÌÍOxà õH@êÄ€£C ó8½úqë|Gü7Áï]ða Ä IH¤¾Îƒoóž ÿgÁ¾@<àù\(ÂÊevíƒácxÂÝ¥0r-¤_GÈC#F͇Ç"ꄘ@‚íˆQŒI£GÃ!ÚŠ8dßÙ¤ØEÙQ‘{K^)øÄêy1Šü[“ÿ*xE-âþGT£šØhÆð½±qäá›TG"Þpƒ[œ½ÈGj‰Q‡düà³HH2I~Äâ GG¾’H’$‹XÉ<^’„™tÑ&=ÙIزq L£ %hE'2’’§´¤*3(Êo!’‹+ b_IËP²…®,#,ïXJ@úÒ¶T)eùÉ>¡RsÈ|$0(ÌEN²˜Í4¥4õ§Ì1ó™³ §3¹ù>oZœ’fé¶Nj–3lçT.©Hòr˜ð <”N ‰SÕgöøÉ É'T[*úÀ‚Öç Ñ(B'ÚPâ=T>SB“wM‹Ž£ñÑh 9ªK&~4þ!…ÏH›SR{î¥îS©vXÊžæò¥')HßYÅŠnô§$ êN+7Ӄѓ¡9#>=JÔ‹ö4ŒÖ¼§ÈÉc:õvFµNMq5T›º«ØÓju¸j:¯v•œaýÜX©SÖvžÕ¬iU+åØê·– ¬T­á\YWW‰!5šJM$S§Ê׬Bµ€e'^áúV¹–o~ÕÍ]SvÓz¦]d?¦X`.4°˜Õ©fAÇYÜP¶fªjI[5ÓrµM³lRE»ÔÖ®±kô¬BgZ½º± %pÉp‰[\ã&·¥cn—+ÕÌb³ªÆl$lR]ë^»`T®Õ˜ÛÇÝv´°þÐÝë)Póž½éUo(ÐÝíÎǽ‘ü®ImKÌèjóªï…h|59_Áâ´¶ƒým/õ;OíêÖ¹£/pñ;Ýqn³À"åï(ý`8ŸVup„W:á[&¸¾ &°tcùàürø¨nncËÚ–²¸²yE±A=¼Ì c¸© .q@]<ãÓÖø›7¶ïxuœMoØÇm2:…,â “ØÈ<†p’íºäy‚XÀCfð‘wüÏSù2¯ÍLl¹ãÔN™¢_3_ÄŒ2ËNÆNÎ1—£¼æÎªØ»X¾°–G\äûÒùÏv¾sÇÞÜ63Ë6Îî² Çlå~6YÑuN3š%ªfFƒ¥Íaþ†4”íeJ¯öÓ—Ö—£i¬çËâ8¼‘îô¤O,jËdúÕ›ö3‘Mk)·ÚÕ£Æó!MM[T?WÕ¶ö4®s}4Rï·×¾åó“g½å[#¹Ø;;vFe]kgÚ•u´¹k6WûÙ‹Ö6±…jinÅÛ£Nöbc|è2›»Ýð>÷GÒ-íu¶·ì>3¹_,ïyûmÚ¾7oÍia³:ÛÿÖJ½=pðÛà×F¸¸^†Û¥Ðs7œ7nhWÜÇ4¸±Mñroû«9>D.€„ \ X€ÈÎáôͲœSñ>¯|+-ŒH‚'`ðpó¡­Pþ«ÔKu cDèÐÀ6… X£@€¬¬ï²ï[[3 ¶Äý¡öJ¹n»æË}šÀ˜Å 0@0P¥§ó;åçø¬‚³öŸk¤ðÁ=îâï’ä%ñqOﮔۤ ˆ¹œn)’O\ÒãÖVä­}ø4·ñ©'îãí@zÉ`ö±x$\@¹çÅ3 !Î&|SVÎÿ{êe¯ö _µUdßüêú0ùV)ïz©¯ÞöÞÞÑÿ!öï݇ %èýïƒÿ÷„ŸðQ–öÑþæL¿úñg/5Ùßï³£þ—@½êUÏúâÍþ'ü0ã àC–à(:oþ €O(À#P)`L‚¸<`2P7;P=€¸b&'°7à=PW‡KKð‰ VÐ9°]âcgp¸0ðƒ0A`y}Ð%€0mpAà–ð‡°P‘” =° ±0 q°¸¸° ?°¸j0 ;p ËÐ CÐùÖÐ ½0ßP ]ðîðƒ ìöŽ úÎJ˜à Ø ÑQ‘Ñ#Q'‘+Ñ/3Qaîòdë4„Cœàëð.ì*¯ H`Z‚ÿV‘[Ñ_cQgþ‘kÑosQw‘{у1?`"Q`ÄJbFîÃJ®”Žéöðê¦1_ž$J`JÀï–€ JlŽÃQÇ‘ËÑ ëI`Nøøø¬‹ûŠâIB$€!@KÀKÔ$`›@«Ë›Ä K€jì9ò±[ˆN ňC?ÀÿÔD`Jâšà8lŽÎ•ä"36’ îƒ%\b5š¤`$8àšn B æ<à# $‘åM ë %"בPH $›  ¹B#ÞD(Mr — "]$'K#›Ò(—à8’rZè.þëP€(ùd)Ãoü¸. Õd,™À)'EïL@ü|Ï,»E½ƒ %"£$@ñ$-M@êr " ¢R*‡R-·r’MH@óþó®d0§Åû˜@(Áow"à44À8/( ÓÀ2ü±1AÓ2< 4‚0“¤@ Dó·Y `¾¯RH`º#OúR6—€4פ/ŸÀî"SM$ .ÓúŽ1y…;Ð8  5•¤/!³ ³:Sd8yÏÞävo&S2·ST¸r ôSîÃ< “)ß² Ú²4™ÒIƒM“>M ,5ÄÚTvR)0E7y“/M³þ(2´R8MÓ ŠsM“à@±2"tZÄC’±ü Å$€P£3M:€RÂ8€`¢#m$ׄDS¢P´FÀ%= ³?Õ¤ï²%>ï:²: E» ¢$æ b21ൢ®ËJò²Iž´º`H  À$À?ëc°ËRÏMÓTM×”MÛÔMßNãTNç”NëÔNïOóTO÷”OûÔ©6 K €þ²¡à:ÀO•H  %F I¹@¸š àUS-A9É LÄSÇ ¾sSKJ"!9™À …t Üþî`K$ %‘dõQëÀ)CÀÇ àbr,ô/1ิþR–9Cà5àŒÑºnÓT«õDàë0B <™ À”nPŽ& ¢”ÂïJ ï€De5²RóæBb2À¦.ÿr GD`8f$< àtS±#W·ÕZV~’Y u ¢._3$;ÓKB.ÝRHUÝŽBu îÃKþò, µaÁï™ `–eá€aM [— LÑ 84 ÂÓF¾î%ÜÒ¼±)©³ d•üNâ/Ô/.<òëŒqe[j×@6WUf¨þ2• ¾ÎFúŽLö)ù“# °XÇvYM9C²V—ài£ÖmÍÀ C¨Ö rh+Ïns¼äë:À¸B6lk6l#ò$†3Mf@m·±mßÖqÃàeé6o—àj—€b¡2Kl¤l¡`-‡@–JlÄpͶaCò ¬gWu¿àäv bÖr  òÊ•ÎõI­¤ï G8 L=5Dêõ^ _Ås þµaomkvüv%WWz› r6v­–ZCSV úîñ:@! Ðé‚·fUX‰y“`— š7d²wzç· ä³ ì—~ó÷ @é8`P϶ ~UJØ"€Kà Ñ ˜Ø‚#X‚'˜‚+Ø‚/ƒ3Xƒ7˜ƒ;؃?„CX„G˜„KØ„O…SX…W˜…[Ø…_†ó7!ùd, X… 1"""+++444<<<MiCCCLLLTTT]]]bbbkkkttt}}}ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›´Dx D'þÏžŧ%£i ‚ÔMSœ ˜Ju©’`@‰"` Á‡@H›"±SÊS4 Øj ˆ&S "ÉÛ2 0m{æí_¨¸pABc Kx Âú y€™rƒ6*!⮉¦¤¿ž& …´é4`!Âå˜ä5²÷µ‘Ãcþi†-q ÇS, ¸u „ÙPm«]J”1Ž„»èBáú¬’ÝE¤ú.âpähÚÃWnñ}Áß ÈV. 4 Õ¬—X‚}ÖIPÄØ¥ÅšNDù€€Dþø$Bc}ÐTƒé5·š޵€x$< Õ"¡Sâ‘,½ùµSŸiã ¡h7Ü ’Õ*hß[ŽEàk¦(YnD˜  ZUS‰æa“jŸ|7`(¢˜¦©«TÛëþØ6ªms,jcˆ'­«´‚æëš IEfæ-AeF>-àY~&áÁ†m‡8¾¶§P€j% `±¤¨>¸*aja,Ú‰ .…ç¡ Û)“м$ô& Þqt³g-­#B­ähÖoŒÛc›6{)ÀßȦF]Â‘Ž¢™îÑsC¨•h8ãÉÕ|,9:¼gÖv)„z‚09…¤Þä®þ§P3R’¢ã$H&‚fÉL=ÍŸLd^-\C¸ZvJ„…©& ,BÈ’Ó˜çiB .c"j À4%I Zî'CJjjÆ‚1­þP½´ c€œ’ýBB1÷òDˆyÕ ØŠÓ,†1DÝ4œÑDƨúÃ¥¶õoæ¡d²–6©rm˜LæYMÒ˜Äh hÔgò­ ³y©¤Ôç*R Њðø©Ø4Á@C0€+èb ©Õ]@·È IÛ#!±¼Ê\ *èq~K 7Rµ¬#™„Øä§6· ¥\aêA‹‚°U (×§† þ¬F5kqÑ­dº˜©~a²©ím)È3V’3Ëâ­Z5æÇˆç¥–傜› è $pÀXü&4A[YÀäPç]„À2‚À’ô· i+ Ý\k ¬xÐ W '/I‰x˜Ë"P‚†‚ñŸšÒ‚ã¤Ü"Ä8AXI‡ÀÐ_!.s!‹]ðÂÝ<æS"¸­ö¾#qx²•5‹ÁôÀäàMÅ`qt³Œ(I2ºm™9„,ø @@>ÅÈ®À< µ¸–D­{ˆí+ñÍkøÓ$Ñ›€·)´F-5í"ú 0'2M„þ9=ù#oôHÐ_aíÒr A¹ ^MÄ i(Gø¬ÉœÕm¨–£![³ä×À® t:J„§b–¶²U[·š¯. 6ðXûÚØÎ¶¶·Íín{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛ[ÝØQ¡ñÛÓÓCðô,­Hð‚üàO¸ÂÎð†;üá¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ·Ý„ùÍJ#@ëFÆótYš{»üå0ß6bNóšÛ{æ6ϹÎÙóûüçᆯŸŸ1'TûÕÞLÿì”ç tà3CÇÏ®«‘Tªþk£êÓ:5vfÅã½&Á;>‚Ö±õi”]g‡ÆØß±vk¤oFÜ›ÑövÔsoFÞ™±weÜ}—ÆÈ©1øi>OGâ—Íø@,þo¼ä÷ùrT~ò˜¯ÃåDZùÌ{þ Gè?Oú4Œþ§/½êÇún´~õ°ïÂë·1ûØÛþ µÏFîoÏû(ìþ¿ï½ð—üjøÈ?2¦ÎŽã'ÿùΗFôŸ?üé«}ùÔϾ¬ÿ îk?öÞ§;ö¿Oþ)„Ÿç/ÿçÓï÷ñ«ÿýL`2äÿÆÓÿ÷¯¿²ó_ þë×þ7 øˆ6€Á`€hYø ˜þ€–Ô€½(7¸ 8ôq¹ x Ø6‚µ@‚"8&8 )x‚0±‚±à‚,Ø0ø 3ƒ*Qƒ­€ƒ6x:¸ =¸ƒ$ñƒ© „@Dx GX„‘„¥À„J¸N8 Qø„1…¡`…TXXø [˜…Ñ…†^øb¸ e8† q†™ †h˜lx o؆‡•@‡r8v8 yx‡±‡‘à‡|Ø€øƒˆúPˆ€ˆ†xЏ¸ˆôðˆ‰ ‰”x—X‰î‰…À‰šxî·‰¡ø‰“牃`Фy£Ø|«˜ŠË†ŠŽ×Š®l°µþ8‹¢'‹Š§‹¸xi·è¿Ø‹®Ç‹ªÈ|ˆjÁÈÉxŒºGŒæ°ŒÌ|ÎhyÓÞy€Ö˜uÕÈyݸø¹hŒà8NÚxçXŽÎŽš'ŽêxìHñøŽó玨gôˆ‚ø8Œä˜k2rþH  9¸ÜpÉ€ I{Ù7‘ÍØ ‰ y‘˜‘Òh‘IÉ#’ è‘ÖP’&Y‚(i|-¹’Kø’Ü’0ù*iz2Y“q“p‘“:Y…>y}4ù“+Á“g`”DéƒAÙ}K™”_Ø”â7”NɃP‰~U9• ”e •XÙ„WÙ~þRÙ•#Á•¬÷•b‰d)iy–\h–Ȱ–l†n‰s——_€—v¹†uÙ}¹—þ —²÷—€É‚ „Y˜‡˜˜ –Š™‡¹‘ù˜0™Y`™”™ˆŒ ˜™™Ž¸™阞I‚Y %wðe;;Õ™£‰‚)®Âþ6„¢ Pm 0?®ùš†0™˜T›B@(Ù›¾I“IidðUœ yœv÷œ¤ œëT„²Ð9Å4¢ ‘œËY ð ÛiœÞi‹Ò9 Ê)œ¿±jÊ×ë¹à ŸFp[óYŸ‘œPJ ŸD€ÀʇÇþŸ!OjC×ðžN ò9 í© äp˜ÀKÄ9ÞµÀ€蹟 ”ô9 úB&wÁ¦Á,Bju¥ž'šª 4Z£èx£È£:Ê–è£? ‘) ¤C:9 Iz¤q°¤Nà¤LêPB¥)Y¥,Y¤VºSJ|Xº¥Ò÷¥*(¦`Ê”ZJ¤eš•dú‚kš¦ËÐ¥J§nZ–gª r:§ap§bצxJ—uZ‘}Š–|ê z¨ˆù§9¨†* …j¢‹Š‡ŠÊ ú¨¸©J‰¨”º‹˜z¥›š©Ïh©ª0©žj~ :„¥:ª'Ù©Ô ª¨ª¤§Š„¯Úªcþªª3)«‚«^I«¶j¤l‡«»J¨¾*…Áú«—¤ºJ¬€ ¬Š¬H°¬ËʬFà¬Ã ­¦ ­ÇJ­«:­ ð¬ØÊÆÚ­ó`­à*â:®Êª­my­æºŽèê Üڭ庮ѩ®ìJ¯ò –ßz¯¬h¯Vɯúj ñú¯è°û©þН;°í*—›°œ¹°œð®ØJ°+[±àp±ë »± ±f²Ë;²ÉÚ« k²¸P²*Ë©ùÚ²¼*Š) ³´À²4¦3 °"{³È¹³˜ ±Ôj³¼}n,%ð`rž›¦i—Ç{ÜÇŽÌÆƒ<·¹°–Ü*ÕÓsŒÙ mÃÄ4üÈ|üÇü³‘¬•ŒªÌÅC§þн†F¡DÆ¢ìȤ\ÊZ{ÊaðÊAó¼”39´\Ë£¬Ëj{ŸI0®öÉx¥ÇÂ<Ì€ŒËVÀŽH7ÍàHgÍ­Œ`ÿ™÷Y¶Ì✡\Ë· ÍŽÀ ê »p‡ Ÿ)wÇÊWÎÍlÆçŒÎ2Ì ÐÏÕüÏýlÍM¡Ú¡ÔƒUMÆàgÁ|ÏhœÏú¹Ï\ +J-ZaI #KlÏ÷ Ñ͹AÊÑÍìÑý™ý$ð,]Ã=)ÒÂLÒ%ýº'Ý#0×1[ôÒÆ2=Ó˜HÌ>?Ëç»°e Óæ,Ôc ã'Œ–½J-Ê? ÔþÈÔþqÃÇ[Õ|ÕXm¾5ÍP'Ð=_ÝÓ­ÕW ýe€ÜÖnÏp-µ ¹‘US±Ÿæ íÖb=Ö MµoI½×|]ÖŠ½Ä’­pàØ[ÉÌØ“ Œ}-pàNî Ö¶ÚI[!0j×!ÄÛ¨ÝÇžýÙ L %ðt ÁÓ­ÚF»‘"‚ÃÛ{]Û¶­é»ã-°–—œ]ܾ=´éæ‚Ù0ìܳíÌÇÔ•˜H­×ÐÝݶ zÏØÑͳҚÝklÜÛM¶! ÙLÞŸÝÞô ßñÝŽö «îíÇë}þ³øÝÛý­Ø>Þûݳî•ÿíÓN³®Þת>ívÝ“á=­ß¾wq ¾±îÐîª0Å$>ÅUÜáûá”øQùýàõ©â#ÍâàâžàŽ*Š þÖ×Úâ/Žâ+ã1Mã6Žà8žÇ@~…éáDþãGŽäó}ã-\äþäÞz®;~Æ!þ>.åO.äKÝã5îäVæVÝä^~äfÖhnä_ㇻä ÞæU^æp®‡ÍäbNåznåËœäÛ*ç+¾çdþæ€Þ–YÙÅËçs~è»æ©MèiŽãNÛtÞçvîèr™èõ½è…®æwŽ“œ¾åñËèƒîç•®ÝSþþé”êÄ]çlê3®éúšêï}éŽê®.Þ°îܲ>ä´~¯¶à’îæ ì+è³î铞àÃîàÅÞë­Žì|9ê¸~ê™åÆnØcÞìûýì<ÎìÛîì»~Ú¬>˜¿æºNí¦líю醮íÒŽîçþíå¾Ùî.îónïì‡ÊìúïÇ.ïÏíé~æý®à®å×¾ìÙŽåõ.™Ý>îüNð¹¾êÞß ¯èOñïTñ‚|ðlžðð òñ#/òǽñÞñûþñ&O y~ñ±ÎòåòÅ›ï0_ðä>ó„Ëó7¯ò-¯ó²ó•Jò‘¾îíCïñÛíò¤Þþ]õEô’PóNïëHáFÁBÏõDŸóXϸaoð]oà_Ýi¼óQ¿öÜ~öô>ö÷-÷ƒI÷ˆ©ô–^öÒ‹÷ˆ©÷Ï÷ªïo÷Ñüö>_ñ‡oõXPõpõMïðOùd?ùßó6?ðoùIßöX-õ Ÿù†ß«‚¿òˆ_În>²¢ÿî›Ïí<×€/ñ§O»„ëb>â%ÞûN|â´ï÷‘{û¯ø±_÷ޝ²¯ðÇ¿÷ Ÿâµ/ÈÄÿù©ïö¹OìÁùê®ù[öÕïáÑO»ÓŸø×íÌßýÒþÅ?þ÷aüèïüßýÂOºÿ¾ý•¿øƒÿüA®þ•ÊþþÖ¯ÿ@`‰EP±,™MçÓR€ŒUë›Õn¹]ïÉeóV¯Ù`$µ—ÏéY$š‡Jáq¤R/p‰oîOP°Nq‘±Ññ2RrÒëòSîî00Ñ/‰SϳÍ0to*3Uu•µÕõµÑvvvÓôi”­ô¶)wm—w•–¸Øø9MV¹9/˜ÉW 8˜:ÍšÛ¹Ûû<œtX¼ÜÌV:Š|0ûLûöÝœ¾ÞþÞ˜ÿ ]z¾L€Q¢(´ÅäÄ6ŒÛCp¯Öµ{· R¼k:xÂ`X¤Fê èô$Ô”JŸîuü²ÑÉ<@è#@!|pò¹ˆB·E|´3eÕ«ïZe]æÀ"!d0v Bùü¶tÜÓ…S']üurå ]/S€‘0ñ ¡Ã‡ ;{ù=ù—ÑÂWº¸sõë÷5g¿Å@ô!#TÇ""@ƒ´ÑV¦?Ì8±BÎο÷ <Ð÷´Ù† Í¶,¸Ì;µ.¢K¼þÁ+P4 IãpÁElEÁ‡x Ì ›0-x1þÐ@Ôh<DuÜQ‘ ø‘Ê;°/Ÿ„X °,ðÓ¯BÓÄCòr”¬Fâx¼ËKJÜ1«­PüÊ„®F8<À€»ýp¼ñÂ!¹O7³œ“Nɦ ±„àÉÈ0Ó„  €Á7 ûcsQ(«œ±ÎH%­èÎIíPt)F3utMK=ý4ÃJAMÔÉ9mSÔ7£Œ3ÕQ]½rËWûÁôÉO%PÓZeÝÔXyUÕ\MµµÓ_•Ô×c uX‚=µQe¥Å2Ùc¿ûPX¡ÝtÚnu¬ÖØk¥ÌÖþÊmuõ]ÁýU\VÉ…”ØgÓ×Àuym7 Í»uÃwq¥àõìÝßSZÕÎ|åDxU…bš–µ`\¾”_lÍm6âŽ#›øÕŠÐ·‹„ ö·_U† dWEîåb,L¶øa™>yåœñjyÔ—§‰yÖŒÇÝX[¦Šç^™-ÚÙh›æöè¨%úSŸ…©9èbŸ>Wê®AJºê¥Ë×馄v×ë´¿¦ÚS«9"zì­9V›n…Àn[lxåfÚl­ëþ»=¶-u›dUÏve_üž»Ïû_²¡î[^Æ-/ÇñI <­Ãq–œëËEOsd!Oô¹ó{ôÖ“ÉþÜôRù®ÈsšÚõÜ‘=ÒͱéfÛáÖ[÷âk)½÷Óß;nÊË6zVx¯Ó÷ÛÑN}öèµÇdz:«>ræ‰ßžüIºŸó{ì›§õòÝwäü,Ó?|ç'ÿEâ§VyÜéGýËËßé°?XõïzôŸýBG@¦äwx“ÝúVW9ªî<ò¨‡@Ā̞Ihš>n‚ã«àóVx¿¾0T*›Ÿ)Ø’ÚŒƒ0tŸy4CöYð‡,ÔámvBÍyðs L UHD'&ʈ±kŸ §¸Œàá0ŠOÜwäÃ6Њ7„Yµ=.~ ‰ÂS_ÃXÅ2:þñŒ&ò"øE ¾Ž9D_±h½®±~xÔbG4Ç6Ö‘ŽBä )"C‘‡Tä"aØÈ=rƒbü—¥É«Qò–\&ÿãÆLš”¥ºø8F?&‘‰Le%õ(¿Vnò•jTâgÉÈZò/…²L¤ ‡ Æ^fp•"e^<ù6@†ð˜%Lf½nùI 0ˆÄŒæ§ùžev¨™…ÛÂÌú¸MZrrÁ„¦G(Imšs€ÝdÏ7MÎ’Ó•ð”æ/¨Nl²³†î4¦>ß'OUÓ™»„e6JÐòT=ôŽ=9‰O\:™üì!BÅÉ0Šæ²œu D#Q*¡’þ™()ôHº“fè£×\àJ ªÑ.rôžWÌç3ÿISòµT9/Ý—JëITŸº¨ÉjÉtzQžÎô¨[´)ý ÕbÞ’“Œjñ’úš¥Ψ'äV¹:U9â´¢MµæS—HV3šµhéNªK·¾¶¬j[ÊÆ¬¾ó®­ë*k¾:Nµ&4–ë lÂz4¬0ìbéÖXÕ<c“ejL)k<ËR³6ãl]CÚYÝ}öcr•)_¯ÚNÓZµ,S-[y™Øž¾¶puälIKWÛZ·Œ‹ícB›µ±žò¸Á­ÛpS\àö·¬EC ÌR]ë^»ØE‹rјW`j¬þÉM©x;$óž½éõ.w§¶Þîµ¶ Å*rµz T¿ùÕï~ù[ è–½aî^œ[‹®µ·NM0‚ÌJ÷Þ¾ í«0:ßR’·Á^ð(yÝøNX±vm†]ºaVFØ®òñ…ë;Þ“˜°&Vf‡Uп6ÔÅ€…±†LUðº5ö+}u¼cÇʘš(.­‡%ÜZÇF-’½IcßöÆÎq”¥,ÛŸUɾ2…³¼â-k¹Ë^–¡•wZ²¨µTD]—'*È‘–î¤É¼j;/z·fð˜CLë^kÚжFÚ¥ªkÄòËÀ&ô¡?-lV;¢¤.ô²9ÝlOÃÙÙÏÆõ%¥ÍllŸÚÚ©w¶eÒ꫼ºs}­º;nr“ÄÜÃ6vG3«êͲû¹ø~÷BâMçnWûÛ×Þ³ÀM½ï˜ôûÙóÎi¬Û\ëRÛà‡vIÿ ûâCµwÄ;‚ð?Wœà9µ->pŽÄã7Aw…ŽgJ{Ûä'ïøÄK¬ð´¶Ü×É~8Ée.`šƒ$';éÉH`™ À9Æc~ïYß=¯þGÊ¿Ñ%®øÅ%0À6à éÀrwxÇõ§CöìRܶ¥R¤! © †Ö@ª¨§;í뮘þk…ðýS€Û£‚""pš) @ üIìwzÙÑî4ùô]ç ¹ü§6?øI î @„ˆ°"ô%ìžYºÆ)_oË#[Ò é¼¥fo:Âß:Ò©"|}ÂÊUseë­öÓ~ù>’©æ{ïç‰îí3„ôþ÷Á|ÓÉ.ùîëêù%/ø¨›]óŸÿ,t?Ê~÷Ww»!Y?ÿnO“ØÌ¦6F“„€ú"_(@ Ð)`¼Âþº<`Ð#Ð=Àº€b07`%Ð?«Ë3p­ >P!0ÍbKO°ºpk°A(ÒKwPg°mðºàWðsP(8Ї¯‹™Pož YðºR° #P ©0 P ;  ­°Ãp ËPoR E†ñÏñ OEÐïóP÷ûÐÿQ‘ ÑQ‘±oNÜîH’¤äèÎH`Êâý:Ñ?CQG‘KÑOSQW‘[Ñ_cQgþ‘Añ6€(&¥K< +ÀEEë¸ÎëÕÎ&OöäúÄèЊnOî§‘«Ñ¯;+O„NøèøÎ«þ>"O²$ (€PQtd ‰ Í«ñtÄ`P€2Ð1?Ö]¬®%Åêƒ? þxD`ºâˆ?޶.G$!òD€,Ì¢;L¤`+8àÀNBàè< "`"§EMî"e »±NHà"‰ `»¦#®„&1R  …à Ed%K@!'…?vÒ[ Ï Õ°N€O&é¤'­ûÞnw¤*‡(þ-E¯Ï÷°]îïAò¯NrO`Š1K¶ÒDÀ,… `(‰²&¹²P` `%yäúô.I/PèÒ[¦oh²úÐ'²C R¯-ïÒ¤ÒúPtÄ-M 0À®ƒ'êÒ#5r2µo,³0€ú>…ÀAæÄ-)³4ÑD2³O0uDÌ6…àñúòXÿnSR @3%30‰`.Ah³À®$7«B”“0»Å)…à AE˜sDÜ,ã0+Ó'³.?xä$S®òH À;e¥%TV³5#³@ñ8ÔCU2]rbO†NR0`$õ²Â$ ½%øQyHÍ+F€  @Þ“= ½<ƒHý1³TK·”K»ÔK¿LÃTLÇ”LËÔLÏMÓTM×”MÛÔMj`.!± . Þ”O3€Êb|´ ¨Ë7«  Ou(“9¥4 •$u=£“Q/µºB( 7‡;`$ ð>`$@Ââ29eú(‡þôèL €T å@á. I;.5@r3 P Ï 51Yå@ä@ Òs `DÕèTñL²':0 ¯ï:tU»( €3“.+ <€8Ã.`ÌDèLÒÓ.àLuø•#“•_Û &µN…`'€QÕ„u"Q¦#,M  å'±N„dO…E.ÇóN¶úØpðµ_Cv þÕ U ô&V>LoÒsL,Q(–Bšñ W5û¾.÷ó-±.=ýQîrdEVhÍ6;õd7Ž5h5ò¯JÃ$(ÛÓ!óW§ÖWM 7þ'U×Ó`‡ÖkÅÀÀHŒÖð‘ ô`óz–i;»~2je5jò+è#17¶d@képi¿Öo¹€dÉÖl‡ i…a…rPƤj« +Ï*ÖOÆÄn­`'(…eÿVsµàÄVLÖrpC[9@[PÌ„"#³óH]ÕØu÷æër3þÂb`ܶ#7Wx‰ p6t‘öX'sU /þ:€÷/ì`WViµToõv•’gõv¶ÖB`U y‡—|Ñ@Ãà|ËW}YºŽèôjÁ`V×—~U!š4„p Ò·~û×ÿ€X3€˜€ Ø€X˜Ø‚#X‚'˜‚+Ø‚/ƒ3Xƒ7˜ƒ;؃?„Cx}ƒ!ùd, X… 1"""+++444<<<MiCCCLLLTTT]]]bbbkkkttt}}}ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›³Dx0D'þO>{p %(%£i‚ÔMSœ˜Ju’ T³N5â¡€tøƒÔhÙEÑ®ñ`@"Œh²ÕˆTHžšQÊTí™§z¡R à‚„ ˆ1 Ø0≠ DøSB ¡?;îûJç¹j(`!Â80©[D*j#Éðê×̈޼K ¶X`)ÆѰ³çdrÿ¾çBåD8ða m"Ü.’{ÌnDÑL'|øÄÝRBÝD ô¯Ô?¢ÛŒ6D€A ðy€µYeÂoéGh!0@x@„O"þxÀF)xAƒ uøÕØ‘ðÀWh˜„N`WsìgÄwCØ–×N"0ðU†Fl€À…`Pßt&p€rDh ‚Æ=ÕßW èÓ.i„“8€ƒhÀÁ%©¥{}©¸èŒ8DlÖt‡D ¸\I0€40ßmpvA|9„ˆE7á¤g- À˜m'[_CjöF}°`¤Æñ†€ €!0 â…D˜  wJÀvDxãmÀf:&”)jÀ¯Ðéd)¯nç¦ ‰B°h£Qö&(¡ Ú—¥šÁ À³Ì'-£»}§þˆI€ÝoË6û-šI•Uz.QÂ…ãI€ÇR…€ŸDxp€Ÿ,`£Pܦ`­&ˆZÀh% W*¤Dœç—QëõUç ¬šìT ‚&Å9–ªŠ a] ÕfH § œkœ‡Ióv½ µ± ¼5Ú` ˜mÊ'Ëi’ á0ÄOÊ›.¬±Ò 6Ý0T qe= Ê^œþ ¯C|@B !ÀL`¥µápÀlC¶@ü+'A“Bê\¸dá°âdW\mif %”û…;3où ‘±¨!!i_¥>[}÷îıÏ @ËIˆ™U=÷Eùþ% N7— !Ÿq¿…naCØÎ8ï~ÁI¸ †óæ;ð·'î4ØBd,Ôòœ=e +ÜñáÂN»TõÁ%ô}0¯éœ³ÈÁ}€÷´CoÉB|ŽTÆ•ÎøóosK8ã%Jö죤Ú ¨Ce~â¡GõÏŽL粟½FV9€st2@ iÇϽ„”×D g†iŸè:ˆœ!¼¦W`™¯XëID'ÕS‚|¤:!\€J @¾%ôËuÝ P*Q)MƒœZΡ‘MQr¾ú‰kS*é Álã¡ “»"Ô,eZÑ ²§ŠKS‹þ‹€¼<¥…B8ÖB×M¥;w@÷(ÃB 6| t¥8=QÌéeR†„ pJxÍù„pA"”@~œ+Bª¡È•Œ„`Ô¢ Î a”†4*ëç;"Ð RI]Öè7úŠ‚´`k£Aÿ ‘¤] ©É2.&ä·Jê|0‘QГpø¡wE€Ì¦Ž 0l€›‚ñ’ ¼ç•J>ÂÑ ñ¤øéý&û©Ù媩»ÞÈ3–³ü]-…##ÐÈ;‚4Bx|9¹|Ë–óñàÍæaq¢+Í:wdÍ„Bô¡îÔc‰Hl$ûA(þ)”AnH8Úœ<†„rBIA #¯é‰hóÚkJu/ ¦Ù"áÅ®(ÅŸ™ høŒpŒr@,[&|è8%„‰§ÙÙNøŽÀK á7NíØì¶6ˆ iÀ¹æÖºfŸ¡JM£éôHÆÑõkD èS*jÒ‚\`5ÒjÀvp E³29HT ªØ áOúW„ ,6ðÑ6¡%.€ZD°N— PDQ¤õ\ê²"à­qòSeâz@káó”€ÂGSak`3ÎÖy<ŽeÏ© ÚÚrUÒ:&½èÁãF+§u”1KàÙÕ€Ö+Û¤.a?jþ,Ôs(‡ÁN_ »è, €Hà€éÒp@VÅçEð6;T àÄ7 ™5›tè€Ïâ’—|‡€%%°ßRr–„ÀC@ŠeS*|á g•:¾h@à ïT.t9ncœÿèx&‘ŒDRKJRÓ]ƒßò` VÆ(P—îu€ ùh*d:2{·Ðä‰ï»o 0€ x [c%Éoç‘Î0|¬ƒSVƒŸîÌ@¾&8mJƬ‘5g«¸i†ÃFK„=É%Iĸ‘ À˜‘ góÜÍ¿ î’ÏuäËnQ% þXÁY™¬{ž9Mj˜„)ˆF€pD5êR»Z%aÚtm$ý."˜ó ȵ®wÍë^ûú×À¶°‡MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎv³?°€EÃ: 'ŸÝ+¬øñÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûûß8¼ýûíQ¡>x³×ˆhQm@Û¸Ä}€‰[üâÙ®8Æ7Îñgk¼ã 9±±fgš¸þ®é¿-w¹Š sÇ>]EOÃæÒÀ94fN Ȉév·Q‘}ù£plèGFÒ›Ásx4ýKoFÔ™1ueþ<ÝW¯¹6ª® ®##ëìû4^ ²SÃìÒ»:Ôþê¶ ‚í耻Ûçι›ÃîtÏ»ðN¾ëýïpð»8øÂ«ðà@¼áOÅ{ÃñŒ¼ Ï ÊKþòX°¼64ùÎKóؽçGÏÑ[Ãô¤O½PO Ö«þõ®O{Ñ_O{-Ä>·¯}çsÿ Þë^ò¾gúìOü(ŸÇ/>à“oõá+ÿùJ`~2¤}·Sÿׯ¾«³_ îkŸÓÞFø¿ŸæñÃüä/,ú±þô'²ý½€¿ûÏ&ÿ]Ôþî¹.ô¨ðÿÿ×6€µ@€88 x€0±þ€±à€ Øø *Q­€x¸ ¸$ñ© ‚ $x 'X‚‘‚¥À‚*¸.8 1ø‚1ƒ¡`ƒ4X8ø ;˜ƒу„>øB¸ E8„ q„™ „H˜Lx OØ„…•@…R8V8 Yx…±…‘à…\Ø`øc†úP†€†fxj¸m¸†ôð†‰ ‡ptxwX‡Xç|aLJz8wyXø‡k燅Hs„h}†˜ƒ˜ˆçЈo·ˆŽ~’øˆ•8‰åw‰å‰˜8xšØwŸØ‰ØÄ‰€@Š¢øx¡è‰ˆxŠyfŠ~àŠ¬¸y©°‹¡þ7‹‰‡‹¶(µ¸½¸‹­§‹¨¸ŠÀ8ŠÂØ ¿XŒ¸wŒ•ÇŒÊ(ɈÑøŒÂGŒŒèŒÔøÓ¸wؘØ·hÞ8ÛXå8ŽÆpŽs Žè(~àx ìØŽç÷ާGò‚öŒâx5—üþøºPm€ €){û¸ ØËøé ¹Y‘5˜‘½Ç‘©ƒYyyx!9’Q’i ’(É ,y/Ù’#x’Ë“2‰‚4Ù|y“%a“—“<é„@ùuC”SX”鈔F‰…JI >¹” ð”b •P„MéŽ;Y•+x•Â@•Z© þ^ùaù•PÈ•ó˜•d©cÙk™–Zh–ÀЖnù…pÉ~u9—ó —¶w—xi‡|É zÙ—Œ°–%–bpG`nY0)˜Š°–"€*ánË; €k `#阈 —ŸD™Á$ޛə† —‹š&€¢ù—¤yˆØ°H®¤?Ra`=Å1¬Ùšqw› pšIàÐnQ›O¥›¸¹‰Æé ¼ÙxÕA£yœ`šÄ>²bÉ ´xœàh±©ÕIƒ 0žý…qNä©B×°œLМ։–æ©mÙÀ Àp8fµÀ€Ãùžñ™mZ1á‚þØu!‡ÆPÏ9 ¾¨´ð šúyZ¡áè—ð©¡q¡Æ—¡Z %:¢qp¢N ¢(ê,Zz"ڢɡ2 /º7Z£h£IÀ£:Z>Jtú£û¤«£DZ“HJKš¤D9¤Ãè¤òÙ¤H¥Rê”VêYz¥gI£\z”PŠŒ[ú¥¾`¤J¦:¦¨`¦hzlj›aÚ¦{§Ûð¦r:vZœtz§®étjʧ¶§y ¨+ú§¦0¨„ £{š ˆš¨Ñg¨-©ŽÊ¤‹º¡“z†’*ƒ™z©ZZ©ð¸©œ:“žZ£ªÍXªúhªlªQɪªJ ‚ꪯþ* ±Šª³Jª^z«¹:§º*µÚ«»Ú:«¿ ¬ïP¬Æ*¬²j•¶š¬Ù¬3ê¬}­;·¬ÒZ–Ôú¬×ºȺ­–˜­Î0¬¯Ú­ÞŠœà*’åú­Áš®ª¸®ìš‹çŠ|Öú®d8¯`i¯ô:˜øŠ ⪪䚯Qê§ñ °€¹¯ØJ°Ù9°:‰°ðê® k©Çj°;ÿ:±Ÿª°Ó'±[ŠK ýjª»±©*°"+‹û¤%˨û–'›²®².«­$³$º²’𱡠³4+¯-›”=»³kj³tù³@{¨B 8Ë©:[´(;³L+³K´OK«G[¯R;µ­zµ]еþèµ\Ûµ¼úµJªµqYµb»£f›†i{¶@º¶nè¶l;•p;‡s·bY·xˆ·vËK»·ö§·‚¸~›y‚K±…;¸UзˆËdk—»¸Vë°»‡•;¹O ¸˜+ ñXÐ €ÀPrû¸›û˜—ë$p, pjmkº§Û™©Ûm7 §|ö“²;»¥Y»L —£pPC0 ¼¾{¤½› !Ÿ7`¼f´—ÊŽèòEps»;Ô;©ìxXðI! kÜ‹¼É žèËMѺ­ëunº¾é«§Â°° €v¥;¿¸ ¹ü+ª’û¿Uº¼ˆ þJQƒ¦b`нŽÊŽàì>Rpt¾,Àu'¿M@ÐjEP˜‡‰š&@_Cw¦̹ܰ½¸ àHÜÄe^¦X©ÃÀ‰z‘Åû°™ '_[ÆUpz ˜ÂöÒ,<_Ó)n–\6L¨ì¸°R|* Å;ŒK´ö,M ¨ìÅðÅ/\rIÀžÌ2ŵÅ|*¨©jgì_j|§lœÅê{ÁB,DÜ0*×Ç r,ÆGÀIàgC@nê+žä©¿yì è9žê¹ ê¦ÃILÌáà §¾çleÖÈj‹Ç‚à¢ü\Ê£ÜôiŸøy8 +.þ7Ãb÷Êž¼—´œ š*M#\_J¢K30ÌÄ\ÌÆ|Ì@{³±JðÌÐÍÒ<ÍPËì²ñHÐÍŒ¶ÎLÍâ<ÍÖ|˵¿æ#ðó1LÎãÏÐ\Îçì±×l@>oVÂIœçÎò<Îô\Ï7{Ï E'möÏÏMÐH{ϱ\k9|¼ÝÐä|Ï%ËŽ 2CÓ ÑÑüÐíìˆh&p``ðlÒÎ)m¢÷ ²TS±4¶À$MÓ5}Ó‘û µYÞö“%-Ô(MÔ¨kÓ`7Ô#½Ô4ÝÔþNM»Píðƒ‚cmkÕ&ÕYý»[ý!`hó!¤#ÖMÖeMY>Š%c -Ô'­Ñ"‹"­;ÓzýÌr=בxÖ~ÀÏÈ1(”ÔpÝÐ…mØ‹Ø{_ÛqÐÉwûØÙ’ýŠ÷¼*àÏ=ØóÌ×ËŽáKAmÚÕŒÚÛÌ®ýÚ”ýÙôë§œ-ÏžmÛZÛªðÖ³½Û¼m¡°­mÚÂ=Üw Û®ÜÊmŽÅÍÀÝÜÑͰÌÜÕ°×=ØÎýÜë˜Ý+yÜÜ‘%mš=×Û­×Ý¡³ìÝAŒÛÁ‘oÌôÌÊìÛÖ Þ‚=ÞÔÚ1ßø­þÝú]ÚÔÝßðßîÞ@3àU àr‹àØà›ÞL-ßîà NáWmáÎß ~Û›ÛÍáîÞ>Ö$žàÎàa]âwÛáêÍâ}yâqâþá4Ù6îá+.áF+â½ã1îãùšã-äNäôjäºä®äïÊä#nà0žä8.ãy}ãn}á*nâX.·@.Îë¢UþäWåTæÔ<æ8]æ(Žæé*åANå.þÜr.æNþægî—j>Ô[îæ5çåzçkžç¾çðÝå/^çÊMè~Ô€®ã‚î­Ž.Ílž¹\®å^>éž0Ýš¾èŠnç_¾å}né†.éˆâŒþη™Îã›Î竾þë¶]é{Mç¡Þè£îØ´ž³žëÃmë§ëŸ.êœn•¥~ëÞë’-ì„}êG~ìÒêì´½ìÀÎÛÔ~é…éÑžê{˜ìÃníÅ®ëÒn„â=äâîêÞíÐÞä垬ìNìênì°~í{ÉíîîíÊ îÏ.ïèÞãõ>ïS®ï}ÈïÕþçÌnØñžîÿþê‰.ð²Þê¿î»¾Ù o¿.ñµ~ñ“wîV®ðöþÙ /òOòÏ– oæ&?ïäð.?ð_Ö%é3ŸÕ5ê'ßì)Ïê¯íM°ñ1í=?ñ?ßîð?ô÷~óNóOðsnðþܺòzÞòOïKŸõ*/õxþîÆ õ]ïôD-ö2?òXPÞÐvÞköïõ…nàíÍÌE¿—Gïï!ïØõ½÷Ä|ß4ëöMöñ ÷ž²€ïëŸ÷:Ïô{ø„Kø¦Ž÷,ÍuøwïðŠõdÏ®Ž?ø›ßæŸ?è•O¸—õ™?ö‚?᣿¥o󩟸‰?ù†¿úoõ‡nú²¯ù¯_ä´û­¿ø\ö;/à`ϯ¶ê¸õ®?üª_üeùûºÏü°ùÊN÷Îo žÎøúÛÎýÛÚùµOýážü·Où×_…Çßíäü˯ýÄóÁÿöÞ£âßïßûTýñøÒŸþÿ±¯ü@`‰Eã™T.™Mç•N©Uë›Õn¹]/òŽÉel "8È 'ÁËŸ×ï-•°¬‚oÏO¬,PÏÐÌñ2Rr’²ÒòR쓳SiA@ãã`ãH@䤄ÀnQ±@p–°–,1wp×3Xx˜¸Øø¸Xyù±£tˆaàô/©Î÷—š÷öº0{¬—;˜™¼Üü=RY½=êA€èíÃèmóÈ:\Üûk[Ÿ‘_pÿƹ3xaB…œØ-txà‘2A5€€!Œäû×' —ú Šô÷±¤C•+Y¶t)¤áËvƒþ:|È0 G" †Ü22\J-H¹)•ùjT©—bN%g æ›9•ˆЖ,”F—ž,z¯®±h­¶uû®”ªq‰!ˆ8d¢& â õñŽS,L¯ ¾BØ—aº‹7n9×1§ä‘…Öw 8'›Øò`³$C´tdÕ«YÛÀ6©[›qÖFÈiK¼‚õû™4[“jÏ:Ý”vmåË™?‚Üü( &›2AjDn <`*4ó_ÀŠ« ÎEžŠùYè¡·wÿþÉsø^Шa3»à@„ŽÄ[Ë8â‚+0„›Á oÁ‰!ê@[ D-Âþ,Ô[„= =ü1ù@ì„B /¹)6¤%Å]|*a¬¤Dä2íBO$pÆ}œJÆ×°8 ì0ŠuiQÈ&d&È'µÎD#Q¼Ñ´ cRÊ.½Ä$Ê/"²Bm´ K+”Ä&M1Ý|3’0áT“Ì*Í<’ËøŽÛ²Í9ýüSÃ<e¢F>w,òÎ+]”Ñ1ûl´‰BÑ<´ÌoöœÒL5uBÎM«©SG+yLtTOM=µÓSÃÂóQ¹.=OÐH_]/VUmm2Õ[MÖV“œ•ÃZ—X³_u=öÇ\oå•Vcõ<³WJíD–Ú7•µ•Ù`åX·•Úf«×þÍkUÍÖ[iC%QrÝuÒ\TA5TÔvÓbõÝ|qvSt—ü–ÐnÿU—^} †1^Sýe“`LÙ­ôàˆ=LØÓ…‹m8Úz!–˜c)îw^‡- W[ŒÅíeùÕÔâ=„‚X—WF"f=^Nç¸>f9䌞ö^Esš¹3mÙæ™ñ˜a&êÈŒ†é}–é‹^7ê®›ºÑªºZ‰š­6¹d¯Õ^ lFÅLiINwë‚×¾»­¶}[A´éþ™k¼1n·{>p»ƒ.uðÆ]Ò{P¾oævîëÙñÌU‚PÉ ÷«ò¦Ç\óÒâüOÏÉþ4t­G÷þÙôØ BÝOÕý¶üuÄeß=Úç´ýrØ·—÷â¡ü<òÃÓÎ}ù‘ñ5úãW?Zù¿šyë£ß~ßá>{ܯgœûòB¾óêÅ~ãáž7~0ÑO]}ÑÇ'Þ}ìãßï­­ßu÷kߘZ'³éñ;`ØhÀÛÙ}OKà2¿Ú50i „E3øÀR„ŽBø(A¾/„)÷†Á³Ow œ iè*&J3Ìùvˆ¿þP#táØ<è@6ˆIÄÚ Ó—CÅUƒ/ •XE1‘~N$]X–(ÑŠa¼¢µ(<(¢P†O£ýW®!Âþ ‹fã?¸F6ZðwoìÛµwFýÙñŽX¼`cèGžð€TbÅDÂ4n‘‡Td ù%GÒ‡^Dã$XI/]’‹&Ôd"9II<~O“×&C©ÃRªÐ“]e$3¨/Âñ•‹<åÿ‰DLvÑ–¬Ìec)¥YŽR’Á$å0AXÌ'S™ÉÄ‘0™9AgÂ+•ó“#.ù¸¾j6s—nìe[©FZó›û»æ¾ÆéÍrB™µLgüÖ)$hNs™ñDç<ÍWÏde3ŽY3"ÊOk†³‘-b»IEƒª¡–ThCëøN3>”žýäD ÊÐŽJ£üó§î™%jFSþž!…ÞH{TR:Ýr£J3*È<¶Ó¡ç%J÷ISÞ±tF.-@=ZBWút¥•%GjN}î©?Uª1™úÈ‹êô¨Q•ªMQ‰ÓŠbÕ©<…ªVcT„Uõ—c}éIÉZÖ©>­-¤\}ÙÖ­’‘­&ͧX³j×Ò™õEBMQAjÕ¹n¡­PìbÛØÆ¾Â¯ä*/óºÖ½âSš¦‰Ãf9ÛYÏN6²Ë¬‹«"ÂÎÔ°u*ÐZ×¾¶±­ÞZÇŒvD¥uLU0Þjó´Ü´­A›P¯4µä+<ùÖáÚ³¶ì¬ìPÐê.´°Ï¥jq%zÜ¢&×þ˽êSûªÝ–FºÞÍ.]•K^çê5³æ=+w7ª^Ô¦µ¼–/u§+ßÜ¢÷ŸönS™‹Ù”ê÷Àþý/}—*à˜x¼Í-0|¬àáDºýÕu)Š\ü¾×Â0Iã*ÞúwÂîiˆ'6âó:¸·Xã°L Þœ²˜´.j‰Qa¯x°3ÆñyLUWÈ6þjO<ä ëø¬G¶nM›ä§ØÉ Â𓥌Ýû²7¼LVm–[\d¸v¹Ãß½²) d2¿gË-F3¬dƒùÆovPœ%¤a˜·ÎknòÕªg8C9°<–0›U\è SÙÐÍáóƒüü,HoøÒþ¶r¤÷Œèσn³£«œiN·fÒµÕŒç%/ZÔ§†Nªµ¬hBç—¿—5®e-ZOÿwÕ‚3¬‡}k÷Ú×fÆf°]}g{×ÇFvmh­2f?»½Äζ±±qW D$Òõ" À7B{ô® ¼ö±—ýì)0R,ÖÐýîyß{ßïÞ‹}Ãh_|ÚÛ~¸ÿýò™|ÅßøÑ‡=ò‹æ_¿÷Îoô¥}ê+6÷Ø¿ö[~ñ_¿Œ5ÿù—O~€ý×wÿúãßûô+¿þ¿Ÿþõ¯~þûÞý¬ïÿxoÿø« ðþñO °…/è²L脎 è†àè’nÞÀ³BPGKÐOSPW[Ð_cPgkÐosPwIPçæä6†åŒ@儎匀6€‹ ›Ð Ÿ £P § «Ð ¯ ³P · »Ð ¿ ÃP Ç ËÐ Ïб>`æ€Q ¦ƒ¬c2’.â&®âCN£ßàøÎnøM þmQ‘ÑúPßEï8Kôâ¢"@èÀ ¿"@¾ä3q‡ 7k½Ä€?€î:±>Ñ]þ.Åpb ? ÝD`HáˆÀ+þ$Ž »¤‚?`Z<º¤ >€àâ„ üÍŠqޱZ‚07`çäùnQH`‰ ²F "àMÔ‘…À(cNÀ±|ÑÝQ¼BÉÅç†è*‘1ô…#oò„ð¿d!I…è±!sã!©…ô¸N/åÕ  óqÿ‘"ˆ (ÐM"Ò@` ÝDòÀBNrR²¥ñ´¢î:'zB ÄŽ$ë!#¯W²$… 0Àvb vÒKÀM )9PÇÅþ€î QH`îÂOX2+…`)ÅDGñ€,¿Dpò,MàèfòXì¢ô:R ¨"›ò&+"'UR-›òÀÞ$.!o"S?³ZR.PS&Ã/½„%%ïr ÓîSL.À3/òä à2UEM 4E,Éò(ÙÑM²0ëÑ€Î1»D4ÙrÚ²¼‚7«…/à°òÅ$€>`(‹ÒO:€PÁ8€^a; ŒQLœ:¥³F€= RÒ4¿äè …®:¯óߥ>Ö !ý°1#À ã¢:+éBeÑKF€  @þNÓGÀ³2c?ÑAB#TB'”B+ÔB/C3TC7”C;ÔC?DCTDG”D/aø˜á&Ð. JTF™ÀÀb… [õµf·ŽQ9ÀQ¼1"鎸ƒ"€%37€S=@õYeó]Àaà`Ò/–n—`fWT[kvO±RK Ž®; QãÄÖLÑTMÙ4m›[WVZ6´ö¶n+× *r 0×r7÷ €â8@E]” ΔsK·"(üPt™@sM×u_vcWvg—vk×vowsWww—w{×wxƒWx‡—x‹×xy“Wy——y›×yŸz£Wz§×A‚!ùd, X… 1"""+++444<<<MiCCCLLLTTT]]]bbblllttt}}}ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëóóóÿÿÿþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›´Dx D'þÏž;:âÓRÑ4 9ê†)ÎŒ0€A% `h‘`@‰@G™¦ Å) Œ5aDŽ "‰Û2 ,ekÆíß§#(Aƒ”˜ÛÕH"@8@@ß?%DØ5Á4ôYÒ„Ÿ„Fq"pÀ ^½X9<&ðàŸfF¤F qE ;KQžÄƒˆ*a>DÂp@¼…ßc½@"¸~XrÛHÜI´«ñ tûõDxwXBÀêP>HW. 40Õœ6D“  I E  T{ Õ^ p™ñþí$‚€|ÀÔƒE X{NµÀw$< Ö"¡SßœøQ^„èɸS hˆ@ŒCl°U`€ŸNˆàÀT\E‚TAP{Äà€°ÆdŠPÛ” Z)¡Öá¥æÄy€”ó5Ô‘u€ã9WJ\I,`àd€r8A ÀBA`Ý%ž˜¥ ðmªÓ,àWc¢¶d€0"˜¥p€À¢…@)€nDXÇçr{剄®GœÇ+°Ê:*l ¡˜‚ÀÔ *¸jH$¤ÐRú*a% ª(£Žumþpµm¤“VzâpÁ|g­¬Ù@dœ Y'Ádj ÚÄžH` X¸G„ ªµGèdkÑ5 „~œV‹9Èj„šÂ§[:Ñ9D0W2„¿yÜêgõçè A‘Ü|Ç ‘+ IàE/½ÐËÕ±ƒ™,c)÷U20³Ì/ÏùŸ ḵ¦j!I1£ö±Ñ]­ŸZw,D§T{¼¿ A €”ÂÓ6¡ß¯D æKt€Ài@|¤p Á¨r0»°ÚX 3×$&rgø5Þ3j÷!K˜~/ aâ`8!\øðöÒîK„b:«æþìxíêæDäʦæD„¹Nš 1€B4ÞìÅ¿†x‹s ¹ðÄ›`</•hÀG=Ü 5n»W lsûAÒ”À~ŽDp,ÁüDs ‚ |>ä—fÚò¦žÊç]í&蟫(¶ý B@Bë ˜Ÿ m~£ÙŒu»Õ*gøã T¶"pLÒÒM ’ŽE`~ô³j(„†WTA Phh‚­pO!ŠA@g¨%ì # 8p€ö%¡Ò¢šÆsžÅ,r¡3–Öœµð-:?aö>'9á…¬|&àt¼mÎ?íþò¤A˜‰ Âá!MPEÞÑ-xC!¬8!žGWR[l(­ €rÛYd^~ˆäÐYSSÁ˜ ­A¡`üˆÀ™Q 3ŸÙŸ¦ôò!’  …°¥†Œ­Â+u˜ÆYj1x½*!n\W„: /©Ü…@À&S‹ƒŒ¥éÌåä0–¥LÏplˆÊb²CÛñ!%"œBN¥J™0S’­a0Üá¸!:nÕ‚½ ì'§ÃO®6wKàøîWýtÞv–ÉG`†FF¸y&Y¢A8)#Âôx7˜3†s’ÓÃ' ‡°<m‡š!EÞùþ&Éq"„QE4Áœ2JšaKñ¤u|Y†F—Ä’lj‚äéig4U‚±Œ¦R+¤p¼òO™ÐLCÝÚ†Sš©hLjßC±d뀨áßDqóÌ)­(sZ£4Æ( °†“<›Æ–ŠÀ u9yjiäо­ a…¾$E]Jìe´¢uÃ<op€¢„€Õeæç)>pÝd@”‡Òl • [€€ÜE?)Ë7;µ–s¥ ¼tŠm!Ú°Ò-¢«ÌiÅø'6³I§ìŠeQeÆ ²é’,š:#)iÉS°&€m´*uþÜÔÔò5šiíVùFMé±k¶d嘊öð26 x@‚p?$àIª ðwƒÕõ“O‚ÍøMPÒp¬;%!(° °/*)èJÀÄ€\ŒÎŸ„á"¦¸àGÄT‘ˆyÉÐ'.rAR]î²ØÉÞî"ÊeêÉ> %iIÃi¯ ˜ Aøƒ1„§`„D¸HX K˜„Ñ„£…NXR U8…q…Ÿ …Xø\Ø _Ø… †›@†b˜f˜ ix†±†—à†l8pX s‡Q‡“€‡vØz }¸‡úð‡ ˆ€x„؇Xˆôˆ‹ÀˆŠŽ˜‘øˆlç~…g‰”Xk“x›˜‰êЉ…Šž¸x˜HŠÆ7ŠšXŠæ ЍH¬(¯ØŠ§Š‘G‹²þ8N±¹x‹©g‹—狼è€À8‹§Œ÷µ‹~€ŒÆˆ ÊÈ͸ŒÁ7ŒàðŒÐØ|Ò8z×X˜½XŒÚÈ=Ô˜áøÝÇÜ0Žä(~Þˆè˜Ží·Ž¦èŽ.ÕŽu@òh ö8ùxhޝçüƒY{6X»‡¹ƒ ð¸"ØÔ° iY‘'x‘}—‘é !ù‘¸0’n`’$I‚)}+™’’l“.é‚-y}9“(!“j “8‰ƒ5YŽ7Ù“%Á“oñ“Bé…F©ŽG)DyM¹”¦ð”¡—”P©RIWY•VH•凞 É•þò–^IY)e9–œp–` –h©†byoÙ–ÿÀ–^@—rI‡qÉyy—û`—²—|™~¹ƒ˜ƒ˜ÂP˜†‰ˆˆi€¹˜ó ˜X ™)‰9 % ’qIpo³ãI”Y™œx™¢ ²Ò8ñöAÈ9š¢ФI +”šÌ„‰°›ƒ7›£@edo¸É›ºy‰A‰ ¾©qÔ(ÑDµ4Â9œñx ¾i› $ð Í™›Ðù…9N ž¦%ÏÙ®XžŸžN äYœæi•èé †œIÀžDÐÀúÉ|÷ž±ûÉ2$¿éãyŸñéþŸÞà—ÀÈÑÀŒÓ# €à í© O˜ špH³c¹2Ñ¢!Ð$FÀŠ*-º¢õè¡´ð¢0ª2š‚7Z£ ‰9ª£É£îé£wØ£°@£Bz’D:ƒIz¤Í`¤¹·¤Lº NÊS¥E¤ ¥VŠ UÊZº¥øø¥ªÐ¥`:dŠgZ¦k)¦BȦj iš¢nú¦¾§J§m8§Q©§xª vŠ XÚ§@:¨‚*‡|J Z¨U¨Î¨ŠÚŒZ|‘©“ú•z¨—J“Žúº©Ñùwš ª®©ŸJªçyªÌ8ª¨Úƒ¬ –Úªhþúª[H«²º§ªš¹z«ÝH¨¼Z¦ú«ö¬Âºˆ¶ †ÇZ¬µº«;ª¬ò@¬Î*vÉZ†Ó­š­Ö ©Õê–Ìš­é«ÞŠ~ÛŠ ±ª¬Ø®Ä ®èÊŽãú†íº®p®ðZ‹Ýú£óÊ®õú­÷ª•˜¯û —þ åZ¬òú¯Óø®x°Û ” ›ªêú°YÚ°Mа+›«”{°+¥»±°ø±’0°ÂZ° »ªÛ•'Û«Òš²+«’.Ë¥"û²Î8³ñj³4+Ž8ë$û«&›³ú*ª1 ´J:´aj´DË ?›´Û²Lk Kû´‹´Äг¼µR–T˰þYk¯BÛµAÛ¯`Ë’[›˜;;¶1y¶Œ`µ·Šµh˵Nû¶[¶ŽI·r›–jÛˆy{·Si· ¸·|k¦€‹l+«n¸é·u:¸ˆ{zŒk…Ûª‡Û¸~ú¸K¹±˜ §–K‘‹ª“»¹¶º¢;£»›Š[º‡™º¼ð¹¤JºªË©Á. °}Áme–§»R°$ -~s?kX¹»¼ ûè"÷Óq0oÅ˺Çk™Ð›°9çp¥Ñ»¸Ó‹gVT×Ùë”Æ»½TZ¾N /rt?༂۽拺ÀpÐ+ Áö¾ñ[èÛ%€7¦iÚÛþ¿ø¿N°° Пaຠ »|„\³ð;ÁÉXÁJa"\À ³Àð@ ™Â!Œ£¼$ &L™›I&`uv§+ÌÂǰ)Ôtl$®Á›*“ØûÇ™ptPršÃ¦ÛŠP ÐÃþU Cðh4S]F|©û¸ Æ p~ãÀÅÆ™H0ö/_<©û8ÆbœŸPtJ žFÐT±¦éôÆúZ¬KÅ¡êãÇŠ ÈÔÉÆD j€*Å:Ì =Éà‘LÉJô9MóVo'“\ŠÆŽÜ ºŸº ý¦ÄlÈÑ_OŒÃ¡þ\ªl0Ë“<É´lÉLР¡ŒƒL·f@t®üÊJË^¢jb“J6 #rJÅÄ,ÍÔ Íƒ@@\–UÂ*LÍmjÍ0S¡œQjl̼Ð>VÙÙœüëÍ® ΀PµqtÐgç,rÞfgò ¹èlãAl 4¾fÐ GÏÿŒ¼!µQ@£Û3•P}ÑÑ]ÐÐO«Îp``EIÒ*½Ò,ÝÒPÍ´ûµhÓ"ikŠÒ.½Ó- Ó ½ÐÛÓú´îV¼:ÍÓHýÒBM´ûE}ÐpÔI½Ó> Ôþ« 0PORMÕUÝÓK ´!–Ö(»ç Öa½ÒWÕŒùÓPCÇ'ÝÖV=Ö9‹’"~s×x-Ör ×7¦×îÌ1‰2j9Ø‚MØk»Ôð[OíÏ]ÐlØoíØÒ ðL¾—×™­Ù£9Ø{`¿o`ÙŒÍÒ£MÚ—+­¡ÝÖ­íÚò Û«íÖzM³™Ûa=Û´»}Û*íÛ¿í¹¤¼]ÕÄ]܉wÜm Ú­ԦýÛÁÝËÍÜ{PÝÂ}ÝØ­³Ó=¦SÝÒ-pn”MØÚ}Ûܽ| ÷Ý ›Þ«½ÞV`Ù]ßÝÑî½±ðÍØò½¨PâÝþßèíÜi›ÜI-à³÷ßNàйߘݒýàÖÍàÃéࢠá >áù}±.Û.áÛMáºÙá½ýá ¾áKâÊmâÞÝlÛ-~Î ®Þ"›*~à,â(þ°7ŽÔÞ»®ã.ÞÉ;ލáãk:ãñ]ã¢Ùã<ýã.ä4^äëäy°~âCÅ0.ä2®å[nå. å ­äüÍä•)æýåHîâjÎÚ9>å[.©].çlîåCþæ¸åR¾äTþ¯z>Üqîçsè)Mæ˜Úçgþçûjèã}çvžçh>•îヾè…>éF æIÎén®é‚[éO~éÎè÷êèˆÔfþ^ê™nêµ*êW®»«~ᮯ¨Nê´Þê< ëcŽë^ëëzë|>ë¿®ëuNèŽì’ìxËëkÞémÞÝÂ.ëž.í ®»ÎçÃ^íØ=íÉŽéa~íkÍíIì%ÎìáêíЎ矎î×zäì^—ŠÎêáîîn™í{NíÑÞíâ¾Øû^îä^Üêî¸æ¾âön­/ïã­ _Ùóžëõ¾ë¯YþïßíOÝ/ïè¾~îÆ.ª#ÿèëéíNñßz_ììà¾òÊÞò4Oï6_ó8òó oé ï¬ð/ïÚEO˜1oò3ïóGÏÞA?êCo®þ!ñ)ŸêOõ±Þô•xõ%oð'ßõ??™Kö\¯­^ïññÎïSO нöF÷ßöyï,¯ðcåýmç½°IoñeÏðº›Ð]õ™öú.÷€OöÝøß…O÷#k÷7÷O?ß/ô‘ïòŠôyùZßë’“ïù—ïß¡ÿìLmøJøß®óÿù)Îú‹/ûTpñOö©¯í›Ÿó¿ó°où¹¯ß´ó®ü¿û§ÏãÅOöǯüÃú¶ï÷ÍÏÞÏ/üwñ»Ÿï2]ý˜ýÚ?ý@¾ýßûNý¨/þQNþ$?ú3Yúº¯þe.ÿ§îýþ þqŸýù_ù{mþÿ·Où@`‰Ea2-™Mçú*FëÕ‚TF¹])j½eóV¯Ùm÷—ÏéuûOŸnó}ÿÐiŠBLŒ¬oªªë0/q‘1)p’²Òò3Ss“ó‹¯4T€ÒªïÑÔõNu•¨Ut–¶Öö7wôS·—®B À€#j9™D¶H¶îÕ9Klú¨Úw›»Ûû|m/œÜlA@ãã`J@䤄¹zT1_;õº¯r 4x0š?„ä:°cÂ`€;…÷°e˜ß4}s¤9Û¸dH‘#»#ùí€&Ç><9v‘É ŠÔ`r¨q"Æþ™Oöôù¨µšAg8Ð$€ OÞ  Aˆ/øpÓO8XWi%úlX±&LŽ¥U€A“0<ñ ¡Ã‡ Hm"³¢W7\MémÃ’_³ƒ WÖ0'i™¬mÛED€óæÜ·Ó²ÍŒ3'öü4-Ä¡-!8Ê$©†2 TÞmVÙêÖ›œc¿¼H0iÝ»y۬ݛ΃+;/aÍŒ@å ¨â..{sÖç¶gKÿ {víL60ðÎÁôío[² ¢—È“]7·îçv¡Üjâ?~Ý£õÓ9§ÁáÚ1aÎÀ0 Ë®˜^ ¾êººú˜¯¿þ 5üŠ¿ ßFb˜pMˆÀ€À¦Âìº %ì‹Bqf ¬FuÜ1¤yÌ/­‰nÂm$’F#\’ÉÃrlÒŽ «ŠI•LÃÂH®„’Ë.E{ÒË«œrH©43Ì4ÕÔÅÇ5ÿq¯È3a“SH7í¼s“6ñ´ Î$é$Ó‘ÛrÏB ÅÌC½’¶?-sNE%T6B)}1Ò@«ÔÑ÷.ýÔE u F=ÕMT3%•ÕPõlµ Sã„´Î6•oTXueòÕ]1­ÕAqµ´Œ,¯ÀÐ×dyìUÙRÇ|TU`£Ö>b›½?f›•ÕOZµ5UlÅåRþ[e¹µ²ÓY£ývÜv,7Ùs9õZpWu_áõUÞaÓívÝzó¸¿}wí·ÚÑ¥÷T‚Î6WWŸm8Ø[fX݇5ÎÎ`]¾0âX©ÙZ‘-&yã”±ëÖµTxÞ€)V™æÏXnÕåcCæÂØSvŽ¢gV~®™h„nf5gŸKæyä—1¸è¨;šÔ¤…^è¦uÆŠ ŸZê°äzR«¿&›™“–9c±ÝŠj‰û\˜m¨+÷í¼OŠT³c;m¼§U{k½ ßpEý#q WfŸ$¾?]Ü¢È/®›nÊ='ÈòK1§Is”ŸîüóÔÇÖxtdÏðþúo´]{üjÕm_ýáÖw–ö³K_ûöà{ RÝew\pîžùZˆ/{â¶Oþ²å›¿”ç%5þ÷ÂOûð;Ñ^ñèížÞú꥟}KÈ?”ûï%çüöí§ä}Cã§~{׿€:žþ̇:ôýO3Ô àwÁºÖï€ìŠ ÀXAß8pnTå;Æ Ð‚Ì_¡öç? nð„!T¡@¸§Þ-} Œá iX¬Ý‘ðýƒ!ÅÔ»ØÕˆô¹¡ s¸9R…H â=AvPk“Ÿ™XEf´O/œàÌ’ÈE+Vq„DÌ ·(½.šñ‹L c‹h:þQþ±ŒçK£‡ÈÆ1ÂñŒrT_ çÄ5ÞI‹y4  5ØG?ÖmÞùE~Ð_ü£¹G%ʇ‘´à$ÝTIL6²‡OÔä&IIEzïn<¢GBN®É“Ž%tfÙʾRM± å w™I[а”<¥S¹È8ò—ÄeštIK^:Ó—Élß2ÃÔLê<2sST¥4I‰ÅDÞq›Ç,ä'/ÉÍûQÓKÖÜKk§Mcšó–Á„å0ÛYLT®ð´:»¤Î¿°Ówî¼§>ãéMS‚ó–d¥,ŸIÐëñ“\ô ¨=‰‰O=:T|…’?e„MÒ ´¢ݧ¡˜ ÛÞ·¹míj=£Ù )öuž}*Wù* T¹ÉUîr™[ ¬•·„ñ­†€kƺõ²P•mt7 Ý~r–°þ[ÍëvMî&Ö»}­xcÎö&t¯´=oSw{ÕõÊu¼îíEË Êù6½Ý½oh‰+_hú÷š•ýo†¦Ë`ðò”Àù…o»ºàÍÖ—­þ¬võ_þöÁv0†Óù`Ðn¸À>p…EðqtQH’ Àñ6¸šð_ÙÛ  ÁIòu8ä %0À6à ã” ù’wð^{œá"·» Ó™=PÈ@5TŽ€_…¸Æô¾svnsd¥é”Šþúöná4h‰`€&` D2GzŒ•~äU1Æß Ú/Åvèñ¼00:jàñ¨4¡!1wÐÆ¥m~oØí _wO/©ÂïêcA‹ZØò„@  €yönïÀ•Ôµ:<ÈÇ>’ÍêóD„;a£–<† %x|ä'/ö'}æõ>{ßÏý•Ðïéö¬•”QRw' €=KÈ»$¯ ùÉWþò)0v”Öþô©_}ëOߥ=Ƙß}æ;пþøÉŸ}ÒnßûéG>øK‹ò¿¿úæ§úÕŸ~ö“6úð׿üé‘ý¿¿LËÿþoüø€ßÏþ0«/ů¯o#P­ÏÝï©o7ûL«<ð!P;°µOë$@µL ë¶® Àë˜ìL„;r sPw{ЃP‡‹Ð “P — ›Ð Ÿ £P §§Oʃ ‚î †nëŠî H`æµÆ ËÐ Ï ÓP × ÛÐ ßãPçëÐïóP÷ûÐQë6`$e@Öa@„ÃDTŽå\.`Pç"Qe".€òHÄ+N0N;Ñ?CQÃ'îP$ï¶Fï+J Î$@ Q2\þDMXÑaqDnkÓÄ E€>d±hQ\LÅØB ? ÝD`Öáš 20ŽVnÃ$§ª‘ „CHkM2à8à`n Bàâ< `› ÏcžO–‘ò…¾± @Tk ì¤Áq `8ðdK@2 — 2 [®Ž ²NÅBòöñP2õV/ wQMA` 4õ@à01óˆJg0Õ C» JàJ™ 3£”K9D’“ €Q?‡¡é>VDÞÁ9 M K2 ÑèÀ>`€9“.€B; )aR0C-àñ¶à²K•Dà0B@6™ ÀZ®¶³3ý³ "ìJì ;Ó”²Ä2ã@ < 1Wþ2)`DÖb@dÓ.@AàIÉNé¡AUXý@—@L—€.QV-·Ñ* d-гX$!Qî<@H—@8\$)EÒG•3WPK{uXË•X©sR— æn €1îŽ Ó@Þ¡RÒ²ZŸ1MY¯’²8Mà[M@6ñ‘WÍaóàÀ<ŽÕ `î€\M`è D69ÔÒ ìÔu’O?VP`Ù”6+6aS6ŠU`yYÙ›@6çuòNK[M²]uvÛÁ1>öVI– LÖVeXÖag6bµY™àYöôÌ’c› <²•@ÜÂîg™ PÁuÁDàiÇv Ö2]fçΉ¡SÂDÀ„"Góó…Ôô؆JÐd a™&-Ȳ²SàR3ÂRC¨mÍ=pÀÚVð) 8m2 ÉªÜ '‰Åõ,B[&\aßVÃHP‰Î#•-ùõ›7†¯€€ xÀˆد°¨|ÕÑDD†õæV´ù Ì¥–Óà¯õTP€} Y@”¬9#u6Z8ç ‡Œ¶Œ‡Öð€M’”D`ìÑÖ=@Îpá˜öþs$*$*‘L)ýníYVHÝ[MêVÃD:)-Bt³V»úÖ*‘Ψe¤é]ø†MìbûØÈN¶²—Íìf;ûÙÐŽ¶´§Míj[ûÚØÎ¶¶·Íín{ûÛà·¸Ç}ílÀ_ÒP-ãŒ`h"œÌ;+c¢¼çMïzÛûÞøÎ·¾÷Íï~ûû߸ÀNð‚üàO¸ÂÎð†×ÁÑP-,e5„<ÛW‰ÀÍÛÈÍñŽ{Ùø¸ÈG>î“üä(϶ÉSÎò–;{eÙƒÆÉ ì»wïó:p£˜› c§®FN…®¡OèÔhÛê”gü–x¿Hdžѧ1uiþTQGÖ­quhtý_oÆÖÛ1vj„½ggFÚ•Qöu´]¯FÜ©1wi¼=wǵÞ‘÷sô}ï€ßÃßË1øÀ¾…GâÏø7,>o¼äÓùoT~ò˜Ãå»±ùÌ{¾ ßFè?Oú+Œ>§/½ê£úk´~õ°_Âë«1ûØÛ^ÉzŽGíoÏûÝÛ=÷¼þ| â _õÆFòÿùå‹øÌ¾œÏ êK¿ñÖg;ô¯Ï}ÙoŸìßï¾øqtvdüz??2Ô~W³ßïo?¥ãO úË_ËöFþïoÒýÃÿü§Cè €îR€¼€€ø ¨þ ¸€8ñ€¸ Xh X2‘´Àø( !ø,1‚°`‚$˜(è +˜‚&Ñ‚¬ƒ.82¨ 58ƒ qƒ¨ ƒ8Ø»«3´õš³ð:²D›=›´ßê%Û«KË´Ú ´RÛ´ÂZµ5«uH‹µþ<ºµhèµ\ Q¶:Kµd›±C{¶zj¶j{´F—m[´i·¶mh·t;•x‹˜o›·|;·~k¢}«˜ƒ¸|¸·‘ð´¸:¶†ë°ˆ{‡Û¸pZ¸k+¹½À¸–›©”»ÔÒ °¡l¹™«I•!¹çªf¦¤[º‚úº[0"ì“{h®»¹°{²«`9¹çÚdŠ;«f¯É>0¼]Ù»»;Y.jÄ>€»g©»ÏëŠØ{‹Õ% »6ºÛ›½~@%Ð6ª«ºk—Å몹° Ÿkê¼æû˜›¿wZ¾ˆ¿ü‹¦l}Ñþð‘ v¿þÀx@ Y–%•ÅÌÀ´ºÀ‰@`kEP•©ºà)N§_„ZÁ\œ`½µ!:‚Â:ç7’/﫪1)¼OœI`q$dgœ)|Ârð’%Ð,|_Ù©L‰ön‰*Äžú € 0Å£ö[:Œ™F›ÖÄNŒ“A RŒd¼àsJŸG@TªVk^üÅrÆ— ÆG0k÷ÆpÜ¿æ™ÄºÓkíRÃ¥Š67Èà6gÈh|_€žµÄ‹Æ¡íùžWœÇ—@ŸïyŸ»`o9ÌÇtq>ü ”쨽à¤\ȦLʆlNj5j8¿þÔÃd&ÃÊ¡œ«Ö0¡[Av3B#l"(\Ë©°¿ÀL°&L$ðÊ,Y,ÇÃܼÎÜ#0¢QÔÌÏlË¿àâ£g% Åy½Ù\ Y¯|pgä;Σ ³ìk8¬·ÑÌÎ…YÌR0¯Q%£¼#Ïôl«ó¼ìe€†Íÿl¬ÿ÷G¥ à:õ¼qØöf -xö<…nÍ;p}ÑI@ÀÑz;pÒ(Ò*½Ò@}¶™ÇåíD¼@Ó:½Ó<ÝÓP/M¶ö' º÷‹Ó>½Ô= Ô½Ð*Y<'¬ÔL}Õþ?ÔaË’"ª[ÕXÕN}цàßÜ0{Âi`1VÖ>=ÖdÍ»O@[ z7íÖL ×q-!-ÎzÛÖ|½Ó~ý×€@Þ{‘†}Ø9ØŠ}¾ZMÙ Ù’=ÙÇXÙ}ÀÖÝÔœ]µézÙ‡Ùš¡Ñ¤Íצ}Ú œÚ¨½ÚnÝÚ®}£°­´{ýÙˆ}ÛD;Úº½ÛsMϾýÛ‘ÍÛA;ÜÄMÛY`VÚ–×ٌܿ­Ü±ëÑ® ݺ-Ý!J,½Ý,íÒÁ ±ÖýÙØM£PÄ]ÜßݰáÙ™1æ}Þã=Îë]Úí]ÞçÕé°óÍÚõýÞÉmÜ1ºþß³Ýß÷ßÏ àù(ÛamàÐkßðà(*à Nàžß+ábMáÿmáþŠáWÍà>êßÑ á#êá}­á#ÎáújâK âH*â×MâÊâoâ1®â÷Jã ­«î]à2¡:ÎÓ.^¥¾áÕýãb›ÛF~¿0.ÞH.ŸAÜ£Ûäìãòå:=äú[ä)~äVþ ž]áSîã_¾®XŽÞc.æ§}æøæK®Ùl®åcÊå7îåLªàÎãtîäe®qnã|nç‰çèU.èZKè'®çTNß}έÎèdŽèN«ä]Î䓾æO¾’–^瘮æp¾émæo^ÏÎßž­‘þîæ—ê©þ–ŠÞâ†îè”~±^ã’ꓽêŸ^êŠÍëk½ç‡®é¯N²è½Þê».ê0yìÃì§>àÅ^®Ì¾¤®ìpí>íèZíjpíží™îêw>î¦nî¿îí?yë;Îêá¾ìÜþµì.䳎êµî¬ó.åÉþîéïƒéì´¾ïÈNîz­íy~ï˜ïY^ïÒ®ð˜Èðh.ðÏïå®ëÙnðq ìçŽñÍñïñ¯î`-òÃ'ìOð‰®ñ'ð…îïPKò…Íò¦‹òöNìïëOóP-ó6-ñmNñ)oñoò5ïò‹ñšôrÞ=nô=óø ðþ7/îPÿÏ ¿óW/Ü>ß¼LïðÛ®ô¾Iõ/ôUgÛæÜÞõ®ûõ¹®ó\1Ô}³Y÷6_öÁ®Ýܽ÷'íÝ;[÷-Ïó±‹ô².õ7 øG/ø!Jø¸þ÷l–nïîßñp²ˆï¾wöf÷1{ùƒ¯øäÍøíN÷?º‘¿ùšoõ•ﱞ¿ø ¨™Ÿð¤oø‰Kö©OùØ®õ«¿±­ú¯Ÿ£±ÿòŽOû{úªŸûv/úôNü(Ùû°¯üú~üü¾²ÎüÐßðoü–_úIýû ~ý/ý“ï²ÕÿýÞâç¿âÜŸ÷éOäàôâ_ñä¿þõlü¸?ýÉßþWNÿ²þDÂÁ@° ‰$ÓñP,MçµT ä›ÕnMÊŠ,¥Z¹e3ÒV;Çg÷—ÏéuûŸ×ïù}ÿ00K‰LÐð±lA@ãã`CK@ä¤K‰iM­M0Í3 4PtT¬*q•µÕõ6Vv–vPµ7פ‰a@òv‹¬ð´øéø/Y¹‰YWzšºÚú›0›ûïAàJè#K¨P‹øyYà+m½¹ýÞ¼û?_Ÿ?n» ™®„!ˤ4a«½h}œ=›¨m½)ñvôødHŠEò+ÀàʲxÐáC†^A'‘$Fþw8íù©¨ìbI¡C‰Õ÷Ϩ>(‘¨dYFD€›–hÜØsäÎwAõü,Æ5iX±cɆÊYv‚H j8ÜͪÁâñŠªî»£ò¢õûp`¤§=¸hHð8AD¼‰óìõÔ·Žå5˜ wöüyߣ   º/H3uª\Éé8ÏÑüé´ÞŒâs 0ÀˆVkËÑn <Û¼£-Àó <A.„K0››¾3oÿR°«¯£Áþ 5LpÁ ©q@ í²¨ý!ñ+=\‘EÀ:l/4DÂ(ú;Q«aÜ‘GÔ^ì1KQ·g4ËÈ!\’I²~l’!S$2;'£²»$§„’Ë.CzÒKçâ IþT|£F(n “Í6ƒ<ÓM:¤¼RL-é,“.8ãÜ“OŸôìÓ9ùú³Œ4ÕÁRN+EÐF‘ÑGáô2BT´ÒHq4SSI=ýtKAMbÌëäôÔ<;•UOÁlÍRuÄ“LS0ÝLTXu óÕ] •õH[í\4ÕZ}=¶Õ^‘ÝOÕbM¥õÙe¥u5WP)ÅuÕ@oÅ-Û3 ­gÚpUVþÜk¹uvVaQ—]>É ×Üíº5ã[hª¥jØLÛÝ×Íw§ÀyÍ[tƒåa(ý•à VÚtžÈ…—mØÆ{å"øÜˆ¦ä-Fc55fŒcyÞ2ä–YùØ’]ùNu›ug aöUfp=†Øfcsú¼wíÙ^/M9`š‰%jòŒÖ髜Ö÷g–£Þ¸©a­zÍX™v8ëš¹>4¯“hvÆÎXéPß69n´íHmVÁ>™Ô¹g.ûé»ÿ+ïQ÷®Û¿}:ÚÁ«pkÙÖÚíu—øñÌ‹ŠüÓï.p¬5}(Ωµ®myOúóŽIþýK¾õÓ¾}úÇY¸GÞ{ÑÁÇÜ~ëËßW±?ôõÏvÿÃûîG@"€òC ë< ðôâ:7A•ÕŽ‚ê ŸI8ž®~¡ XBB*gócáu’ÂÞP@'ì›Â2z8â‰t(=Ô5/€+¬a‡ØDzÅÏ]<$¿'Â:‹¶(b÷ŽÄZp‰Ìâ#Å.V1‰WŒÐ¶~8F7Êþe‹ü;£ÿ¬HÃ5 /lo|`÷$Ã0*ñŽ6Ô£ù'?’‰Tã ‡XÈ~In­kcÁÈÈFB±¤›${HE:Z’˜4¤&ÿæÉR‰ ¼d8ÇS¦ÑŽ£(Ué8G¶é²¤.YËÞ’M¹¬ ;YÁTúò—´Ä%)gL/*2–ÈÔ 0yÅLÖù°˜¨|¦4IHM/ sD³Üb½†ÇMz³Kà,Ò.‡)Ns \Rg•܉Í)¾Ó…ñT˜5ËéL4~ñ˜øt >›4Ï,±3œèûÊ$ƒ&ªž¦ áB÷¨Ì`ò3Þ"æ=µùOа¡Kz¨€"êÏO~þ¤­¦+' ÐmòRŒ(-_H+†QÙ‘3£O,©LSÊJ ²t’°¬$LÉSòÑ´G#õÏF#iϦµ§1´é8™ºI§ZªùC*”º©^u‘YuÞVwÔÕIUµ”&}¥XµGV™Ul ¥§\Ùú:·Šlªœä¨PZWâÝõey½jZ;zR>”‰UìbËXMøUz>5#]!JY’Zv©DÐìf9ÛYÉBv<€]\µµS¾¾t˜¨ÀjYÛZ×¾¶P)h{#Z‘V£¦u©GSÚ¦s¶Àj6OË[°F3¡_ý-p?;Êáîu·†ímQÛ‰Ùå–5¸ò¬DƒݵBs¨þ×Ýgsùܧªµ¥àíku•+^‡fwŸæÅ*z»«^Ô&×·î­)y—)_ÂWºÇ /~©«ßýJÕ¿Í,ìw§V;ØÀ\…oA·K_âz7½ Fn„±Ëß‹&øšÜ½°}ËÞür¯^©uϪ[ØÄFqŠÌ⸶w8ž«Žg|Û ¿Äý\p†¼Þo¸Ç>Vñ7+<äújxÀ9>q’uöc‘6À .ò}¥,c*WyɵqiÇœÛ2ëôÌ_Žm7„[4ó¸²p¾¬œÕl 6W9È9Xš—Æg¹ù¹Î>²rMó|S´*8ËDŽ1„ ;gÈÍ{¦³W§çJ7ZjƒþN*–1üä-—øÈQÆ´5-áBSÕÅP6r—=êL‡Y»§Öëyp‰‰üßä ÌGEóB»7†¹0,,ÀWÀ@|*nmœº¤Lɸ§É¢tV9]r8GþÍRÎJd¡!WàÅ«ctl 9Ñm„:»E”±êì;”:hN’’•d!¸Âàž®¿ÛÌx³±Ò¾ã• ¥ï <ÿÖþ™¥¤$OAB â>÷º]ï’þ»²7ø9O^$–”æ£XxÏ ¤ WÇÂbs„­cî  ÀêYßz׿ž€„b=°Ûß÷¹×ýí= X!LöÁ‡½ì7@ûÝù½Oìï…ß|Ö_±@þôs¯|L0ßù͇~bkO}ï[Ý÷þô;°XñÿøàÁÐ?}õŸ¿ý¹/¿ñã¿û÷×ßþæÇ¿îÕ/ýýãþþþ÷«péÏ0þ}¯ç>à±L€ç|î  è`èä „ ³:Ð?CPGKÐOSPW[Ð_cPgkÐoAÐæE5XÃ5°Àä|å²€6à “P — ›Ð Ÿ £P § «Ð ¯ ³P · »Ð ¿ ÃP ǰ±>`ŒàS Œã’Ã0ä£á.â€;·Þàì>4ßÖcßîP‘ Ñå íÍSèn³<-J $`ÔP*ôÃM$‘- àC³.°M<ÀêcÀ@@ù%áÚÐS<`%Žþð"pOD@à.à ¢bß8ÀáÐMpQ€‘À0,¨ƒM2à8à&îB@ß<@Ù¥›clPdÑî<…Œñ @k ø¤ñ=cñâdK ß# *Úq_t xƒîÆQRÞqñ5DÑM òåÑS€ÎOî’_@ÏêÜâQdq=`êPÒDà ®  pO@Òà>` `÷„ñ¦B$ J§åðšâíÆ%`B ¸î#Íñrñ2ð$‰ò2<À%Øã&Û¤À Œ‰®]ÚN'ïSþH`Ö¢Q@Ò*)éQ)³€çhrO$`$M`,M`è^rZÔ"ô0rT ¢r!•r&IÒ$͵ €OØRñ B-çã/ÙÅ`[Å0ô²M@R"¯"“20± &÷äÎÒ²9 @2¥MàZÅ+Ár(Õ‘ÝÑ3w®-Û¤3=ñó1$À6Û.Øðñ$Å$€>à'ƒ²Q:€&Á8€4Á; ‚1Ns€9K`žÑ: $EÓM†ŽhÑçž3:µaÒc=QR0`¢- €³ä#V±ÛD>5+F€  @F“I þ³ÐÀ>QA”AÔAB#TB'”B+ÔB/C3TC7”C;ÔC?D”Dr³à: D[ôÊ1îC&A7 J9²T²>õÍÀ>`À6E.à?;@$1’-C`*àÐp³ HçtD`0B 3‘ À ®ŒÓ¬±=:à°òà 3JkB%  )ùm </'R$` CT"9:Ó. :5 ó”NSÄñ”ôfþ`"§R’9D’#?ÞcáŽÀ4Xô C?DR!Q´UwÒ HUU›ÕXÕöô  ê|•)² Ø’9& 4yÕ£Ôñ"W{qáØ²' ™ÕYÛ> ÜÃUM@+}UNMr LŽ9:ó@SÒ!CÓ\‚LÍUM@©ôØÕ] UZM௪nb‘ 3·µî+X¶KV#Á)ÌõS ö3õÕaW¶ u^/Ö^€V‘ÀVs¢R%±àW‘‘èF ÒôX…Qä[YÖhóÀ*#¶^»´ê• u!äcè :8 ޱ1²›C(ÕRS‘Õ<µU a± ü˜ñh×< ^•OîÕ-£T"`è"°0‘ô(.k»ôKÃtLÁÖX£ÕdeC J€nÙÖqWá2ç r—rõ Ž5EÏÀK+×só!fB7÷ &÷sOuSWuW—u[×u_vcWvg—vk×vowsWww—w{×wxƒWx‡—x‹×xy“Wy——y5'!ùd, X… 1"""+++444<<<MiCCCLLLSSS]]]bbblllttt}}}ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««µµµ¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›³Dx0D'þO>{p %(%£i‚ÔMSœ=0`ƒJ8  @ƒF´rõ öR£gwU»Ä® DÑ€#ˆ@¢“¨¥LÙžyújÅ @ðà@„$x ¡AˆDž\ùrf?%Dú3ôèÀ?¡„¦«q"€»x°6R¸ ` ‚ÍŒ<µáŠv– ²üˆN§M|0@è€è¨êmb¸ñ=‡ ÂárÙ$¸ÐÍ—¸™ßÜÃx/ÿxÃ4g@ƒ"0D ( jJ€ÐU@°›P$PÙIPÄþçWq¨‘"à>‰°`•Ý^XœŠT-0 PuÀ‰IèÀxE$·”émÈžs;…°¢‰Fl€@€`°Ÿw4pVED8a ð÷ÁŠ<°›O#¬x¥0a…J`×›˜Íh&–ö5Ôu€çÁ%tCd÷Ÿ\^7ÄtƒQÂÑ ¡AI$€4@ÝnPAð› /QÀiý”Ö,ÀØe¶ö¤c€`Ôt1ªP€@¥’…ÐiÐö`n&Ê\^}AÛë +Ô °*­ XƒÀÍé„*8pBdŠm§þ]§A€”2pi`¨:&.äšÙ¹[ ùA¦\pã ÷m¸ãÖÙ›Hø&£I|Pg–1Ð(Sq£Dxp@£pÛ^Øù'Ät :Û #é’&Ä|á9 dÝÚUëËl…Ê2Ê:€ÁáÁÈŒh€D°?£§²ÿB-ÁeÎÆü˜  @sT»M÷¼² w8rb§•€ô0›BW!›-v ÛšXsj'6DÛ1Ý3”)|PB ÔD \fWŸ Á¸oB®DœÆÇHdJ§–.YäÒé}öØB´"p>·Ÿ½I¦þŠlÑ**ϦãÀëÑ&¡m½D 6´ÓpÂ@ÕÀmMD°JÃÞ¼!˜>Ê© 0ÄèD¼º ˆ}n‚eËéD½ X/övó<)}¹WøB‘/9²~"‡»{µ yß_úË J€¹×iH`ÔÌf/DÈΆp+áÌT‚ñVŒ6³¹,e¨#îŒU+mh|Døó“E5E²Õ‘x£8GíF ŸÈˆð¤!ÌæA%¼Rf©ý‹À1– z(„HF„Ùá˜ø>„ Eˆ ¤š$Eþå‚É!á³b€.a=Å{XÔd@ l:!TþÓò! ±¥/ìû O¸7âÔ0u(BâžW¦ ¡xe‰áƈ¤:’-ë1 Òöò¤Qm$•`Ô”F»€¥’wB$‹àÆšR–¬"B’ãAkùì™Úâ,ÕªX–—3ç’0‡’ËíˆÐ• ~ðT¼Z’!áî;4AP,$ÐÏix i8"$çjÀ¼¤™Jê…9UÙ^/{DœÄùðœ|f‹@EUd82Bâª<Ü •Àbþ¹Ä€&Ac$Ø|IÏA&NéBôê(¾í¥m®[D— ½àTt{‡ìæ"˜AHKP¤(gþÈ>å !XŸ‰ !?Ê7¸ó|=ÛãÐö|§œèÓi J{"ÄR²¼“KE¤G`7w ÀiîôÔ!DcÚ˜4°Ðue›ü+K×JÆ¿“INk¬)»„˜Á;¶Õh&ÈÚî@jAüŠî’g±¸3%¸©M5Ay,e…ÒH`%ÂpàÚ—NÇPü³T؈CE’¥Í¬tÛW™S–¢êok &_OiÓÕ #ÈËxU›EÎÐR“Š€» ËïѦ]´iš>p€e •'½ l 0 Á\›*@¾ˆžA@¹rDK[¿,V‘)Í…À «×!DóS+¤ ldCþ~"A¥Òd)ír%­yñÓMØ”¶ÌØÊš¹œò[Õ%Ü7.t½)N±eOQið×x<{Z-x`»Ui@‘@P…IÔ à"aî¦x’Ö¼d@Ä 1/µ'%„Àp1ꥥqi|PÓ"P‚ –Ê¢IšJ´êÅÑ!ä¸*<žæS¾KR"€Àj €\Wôæy>QÒ…½'XGi? †„l,Gè½Ek2æÑS, Œ%<Цb—̬ٴ ¾Bpà¡ 0€ H…°×`¨!ö^ÔL‰yVC£¸$R"d6ASÒhþLÚ]æôÈ×Èꉅ>8€1y®¨Ý@‚@š|ƺ“ E2MŽH…+VÒí¬á ®LŸuxg õ°—ý7Õ–˜Æ™Ž²™M픸IØÊ5½ŠÐ€àÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~ûûßóþÀ° ð„°¦&Ô[´N:üá¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹ÈGNò’›üä(OyÄIðM˜ôhí¶€Ûüæ8'7rÎóžÿ{ç>ºÐë ô¡ýèê.Za±pxûÞÆiZ ½þX¦°ý1v5dM ®OÃëÑÈ:5—F©¡Â޵رöh´owÆÚß1wkÄÝwoFÞ—Q÷vô½ëÚØû2ŸŒ¿¯ÃðÒ`y5O ÆKñé€|µ'ÉŸÃò”Ïü0_Îkþóuðü8DúÒ¿ôá@½éWŸÕÃõ¬ý`ß ÚËþö]°ý6tûÞ_÷Ù¾ï‡á_ÃøÄOþ_ æ+ÿùEpþ4¤}èS?ì1¯¾ö·p}htû¾ÿ¾Ü³þòSAüÍ@¿ùY¯~¾“ýðoBû•1ÿøk¾þÈÀ¿ý'¯côÿÌöÄ €8k( X€y–€ÀÀþ€ hTè ø€U4¼`h0¨ ˜ÇѸ‚ˆ"h %8‚5q‚´ ‚((,( /Ø‚/ƒ°@ƒ2È6è 9xƒ)±ƒ¬àƒª´½P³Ôz³Rk©Q›µþzºµ\«µCûµ ɳ{H¶b›X{¶¿j¶€È¶jzn ²Tû¶ö·`µÏš¶t[®a»·ÙÚ·~»¶s» x»¬z¸îj·©9¸ˆ+²NÛ¸|ë…k¬‡ ¹˸¹0¹ÁZ¹–K³ŠÛšË«œÛ¹¿0º¤[µŸk›˜{ºrû¸¬+±^ûº»² ´´[»K{»¸;’«{ Ö ° £ŠÚ»»ëœÆK$ -”Ó@€le™ºÇkšÒ›oÑ@1·MÆ™Õ;½YÐÐ<1çb¡{« hüsP¾Ñ›¼Þ;Ÿð+°21w§f¾Ý¿VÐÓÈ Øöþ–óË¿Ћ" LxXp¾³ª‘à Ðüª¦‹ÀNº¿‡ÁªÚJ¡£b!Ûë lª ùÀÂ0àHÌÁ¼k $pÓÆ­I9é)h7dŸêÁ4,¦B¼¿^µ™ÒÃ%@u`À“¡z”äûÛ™—6aMúëÍþL“½¯Æ=pðhpÐŽÚ `je @eÜ[sô¦h ºíbÌApoùqÓÅ7Óèq`ÓÑ;0ÔD]ÔF}Ô@<í· ™³1)\½`T]ÕV}ÕþXm°Ô{«‘!ÐjÔ!Ä‹Á0ÕY}ÖX½Õ9­ÓO’dxö–fÖt­Õ\M·3)@9#]Öu]×jÍÖm»Ö}`bH3)»FÖsý×Y؂ݺ¿àóâÓ0­ØŒÖŽýØ‹ ’ü<Ï‹}ÙVÙš­ºÀÀ§ç×¢Öwý¶ñÚ«mׄ]ÚHðÚ±}Õ¤MÛ‰`Û·]Õ¹­ÛÜÚp Û«ýÛÀ]¼ÝÛ²}ÜÅ9Û~JÜ¢mÜÌ-ÉÝÛÒ=ݼ(Ü[ªÚÊ}ÝØý¡Ú½ÍÐ}ÙÞÿco•=ÝÕ}Ûå-ª8ýݵÞrÐãÍØíÝ¿eÔúÔJíÜE»Þ±}þßUРÜT-ຠàÅÝ“nàË ßØ,¨õý×> nà^Ú Ý ŽáòÝ£NÞþáÝâ/:âö]âžáš­âÎâ îßA ã€-ã'Nã=kãtíâDlâÖâÊã˜ãA®ã8Kägíãmíá-.äªämäì å*嬽«žãá±:á7®åN>ã]ÞÌ’ËÝG®¨@^åH³XŽÛTàVΟo>Úq¾àmî²uîÛwÎáy¾²{~à}NâN²þà:æ\^æº*áO.æk.ç…þ±‡ÎägªèiÎèâ9éô æ=>è+Îé[é ã¢>±¤éþ®é›îèdNÖ‘Žç¬ÉÿêéE®ê¯®é©®æ«Îê»ë½®ësŽ”¶¾ä¥æ³þë‰ë~žìÃÞ”hÎæ¼žëŒ®ìÜËì„îì§Þ ôì×îíení(ŒéÒîëÏÞzÑ.éÓ¾è¾íYìSŽëì^íçÞ×ðžåëžéíîêó>îØêÚÎïúþíÔîõÚàšÿnêÿå O½ ìæîî ›î²ìßåâ®ðßñÿÝÏ}ä®î/ðå¾ì!Ý#ß©%ñûîðò3ïñ¯¿÷çò>ðOñ†™óv¾ó)Oï>Ÿ Ý^ó.ñŸ^ôóÚòßûòÍ~ò2ßïþô,óR½ò õÙ.õµ®õ¢ªô·Þð_oõ[/öÆÎôëêôgöÇÍöaïöÀ ÷øöñ.ç]oé]ãXïÐ@ÏçBoò‰þÞgK÷Îõ¯Øû½øDÝßjkøç‡ø ñT/µoá’/ñ*oö|¯ö;û÷‚ø0Oð•ÿ´—ߥ™¿ôù>ô\{ú;m÷øNù<Ÿµ®ÿãrù°¯óß÷r}û˜ŸûA¿ú‚ÿµµßäÀøÂ?ú­ÏûŠ úˆNú³Oó¥Ï´ÅéÇúÉõb[ýÂzýÏïï¾èÌoÙœ_÷áÿúçOéãŸèÎoéÝŸþÆÿ£¾þÜÛþÇ®ú²Ïú´Oÿãnþÿ¢¯ý@`‰EãqT,MçµT ä›Õn¹]ïÉeóV¯Ùm¥µ—ÏéY%Eš—Ráq%S/°‰oîOP°Nq‘±Ññ2RrÒëòÓ¨B À€i`””¤èî00Ño)UoÕ­Õu¯*Óö7Ww—·Ñ²8nA@ãã`ãH@ä¤äo6 –ÍÐ {M{Û©;xœ¼Üü}ì7]«Tˆa@¹ö <Üž0j?V¿|âÚ4xaBV}|€ˆ¨FD14‚ÏA4ßÌ&+£Ç†#I–4‰pÝÉs  ƒ‘ehâ"5þƒDªáN㙟ۂª4ziÒE)•ö*À€ÈŒxÐáC†pÁØÑ¢7` Ú³iZµkÙÚAÛöÒÓ¨S³ˆÐ`Zµa}š%úV¨_kEá6|X!SÄ“Xºd¦…€¼;ÿñM3t0`3˜g^ütè\ŠE7zQ³‰ÈÓ$p@¯+ÏêwNM†³ìÛ¥y÷ö­fá ìþæ]< èÕ½;yçl1¹SICýõãÛ¹wÇBÚ;›a<s$T‹E¯áÔÒPµSÔKÍoÏLùÙ´ÒR]½“ÔW'd5UWeSÖ\_UW!Œ„TS\ƒå³×b#åµ×_QVÕ€h5ÚQA-UÙþZmòÍ'§½'[F£ý–Ndu­¶þÇk·ð”›m‘@÷ (Á}—@qs%—ËZ³¼•Xxõ%R^YéÓÞ?ñmvß‚cìwWLmuVØ6bvõ_8Öá{#Þ8^u-­X[s;íöS‘ÝztYŽUþobjÖø#”­½8å•mÞ®eQAö–هŒ¹Ü›…>.çPw.™æ™{†yè¦?+úã—f8ߘŸuëÅ žôètM¾èz—ž:ë²ÛÚZÒ®Ûõ8§°c³åV íc¥ŽÛꆩ&x»Pµõùš[·-†»f¿—‘í´ïNüç«÷öyñÊkÜîS•ΘìÈõ¶t”0üñÍ'gº/ÃCuÑ 7þºô 7=ïª[¿ÝÀœ§×`wð¤eÇørt—VóáOïNñd ä±HDC2‰‰Ó" )G#r’=”¤˜(™ÅG°‰™Ô$+(FzÒ’[$Ÿ(g¸I+uÒ‘©eþX‰BWN –dĤCYËÞRJ¹¼ä*¡ØK_r˜Q¦*_XLZS‚Éä× OK>³™ÐŒ 4ƒ´ÌYŽ—ÏÔf¹ $o:œ›!Ç)ÃrþèœáLg’Ö¹¶*¶Ólï¤Q<ÕùF{2rŒøÜ&)×HMv¢òšÌÔ¡@÷§ÏƒôŸ•Lè7yÈPó9Fü¤§?ƒ'ÑFZ”œµ#D;ŠÐbsþ¡ ½¨HIR=z4  Õ¢JWzÏRž±Ÿ8ݨNiZ<Œ¾H£^âèKMÓžnï§# *m†JÈ¢Vó¨5]ÙRÝÔÔ6ž«ž%ªTUFÕûXY%êVË—T‰¹Ô©Ö<©L?IVâ™õC^¥!X)Ö´ºzpõ\ÅS¦ú¯•ÓkÇLyPµ ,è ¾V®W]çPh\³™Õ¬f¥±XÚ´ …èS «Kb†©UíjY ZÏò¦±zl)Ù“ö>¨ÀnyÛ[ßþ¶`ékC[–¡Õ¶`Ãí\—ÛWcW™Ã$rÃJY˜B¥3…n0¥ËIêÖÕºþ¤ía±»]\v÷•ßhy)ZÆœ>×¼ðDïyE«UÓfó½âÜ)|ãûP×¶´¾c½oJå©X¡6·¿û¯"Õ{ÝÒf·­vo‚3:_îø®–eb'|`þRø¬ ž.†“[8Ó6²Õ=1ˆ½cÜðÌÖ+ÆjxÉû`ïÓÂÑ%±Š?\UOöÇ7þ‹[Ü`ñÚWÃåð.…×OsÇà ²r{üÕ)7¹4DîŒg|åu¥XÊUÆò–ŸÜM#×x¼Ö®„™©ºÛí렑ˤB•"Dø<@>h‡ÞÊz»{¶ñm”ÅêñÒÊûgö.„B /xÂGírïùÛ5ùćÜ$¦”ê+8ùÅ4fì)Âð2„´að  Àîyß{ßÿž@f=°ãùÉWþñ=€YQLøÑ¾ð7@üå_ûÍ¿ìó¥ß}ÞS³Àþø“¯}hpßûÝÿe‹O~÷›íwÿø;YùÏÿúðÁð?~ýß¿ÿ’¯þ¬/—ïÿ Ðíþ•OÿÄo‘ïù2«$p Ð#0é$ ÂèD‚  éž®DµRPW[Ð_cPgkÐosPw{ЃP‡Y0èî„冀9Ž æà`挀6à6‹ «Ð ¯ ³P · »Ð ¿ ÃP Ç ËÐ Ï ÓP × ÛÐ ßp³>`LAR ÈãÎCALã4Žã éPN³æßà”cM`à NíÑ#Q'Q 1à eðTËõØ¢"`@NÁïBBȤ?1‡ÀT‹éÈÄ „þF±J^(.#Ŧb ?@óäD`ŽáˆÀ. Ž2ŽÅd‹ŽQÀ A>Ĥ >€Àã„ @™^’C  洛#RH ‰  ³ì"æÄ¥Qé¤ùóQ…€á¥è„àè6±0 净óO [ñLr'eéø°ó"^ %b¯ì¥;! …"M@`‚" ÒÑJNÒô‘&úQN8/R2&†€%]²X*Ïú.p"+4À ùñ…@!KêÈ&3<À* |©Ñšrþœî)£ÅºN(-… #Mr)·òó„àÀ&_2-àèvRN$@%Õ’½ò]`eÏR °rLNR'W20å& `NìÒïLà%èÒzò] ÒŽ®$=ð0Ñ’.1ã²pRNÚ… #׃ßÅåáå&¥,ÏòN`2ç1Rq"E“æòLÚ7‡ 6ò6õe<Êc-Å$€>@b1)Ϥ8`€8@’±:€É$:§³:K` < XÒHœn?€=“. :  ÞZ ÄP0Àk¢’- µÀ!ÓqqþL4@G€`+¬RÐH`µö1 ”/C3TC7”C;ÔC?DCTDG”DKÔDOESTEW”E[”–Q@z!%Ð. \TGƒÁàFÀ?GèS1`Gt¸2  ˜t4½`1‘TJmaIìr€´Ó*wîàA$`žrÀ4fq+TM à¼qp3%1àb±Rò#íRàA ³Tk¦TP#A–@ Úr ` `ãVñãÂñ:`Ôé :Á´+ô±¦òà@`<"¥bÀ=D@*ÎÃ. àÞþ#Œt+ÏÔÕVa…K…`+ ®"ù´RB¤"ý‘1cÒâ”rTBBRr5rYs>pbõV·µrÕU @ì„®î솠-Õc¢cR2”5ÁÔó’!%ëqj”-—µ•[ÿ•> âaWM( PMdUbN=œŽµòqÉuZQR%çeÏ.›qLQ“ad×À[ÁÕ´”@ìL]õÕbn1ëY'v9&¶“a,©õ<Ú’cYÖü5d ¼µ`U–\VX7¯AÔc@‹ 8œ=Lðf-6OÝrDêÍÎh·– 6Hö`ÅNR9€Riêœ܃"@-s9BuT"—Öòª5v¶`êï¹Öo·@hg4\»a‡àK@$Àé4¯F‘ö>ÎmÛôMÃô^é¢rsÖj… c!L aÿta*Ö @3tO× `ã8 – Úuc—" Aùo LWvsWww—w{×wxƒWx‡—x‹×xy“Wy——y›×yŸz£Wz§—z«×z¯{³W{·—{»×{_%!ùd, X… 1"""+++444<<<MiCCCLLLTTT]]]bbblllttt~~~ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›´Dx D'þÏž;:âÓRÑ4 9ê†)΋H:õ €@ƒ‘T¥ t”éÑA£8EâÁ€ DÑDê‘D$Y[f@¥iͬåût"ˆ ˆ/H°ƒ# pá Š X Jˆ k‚©h@BM:MDˆp6&vàmm„ð¿€U—A+¼tàÂèpÄó!#ðÞ|‘oâ.(‚‡>,É],ï"ØÓT7^|LúàÈ;†üD’ŽùÐ@@€ FDü ú áh¦Tj •Z l…€Dø$Bþ|ÀÔ‚ æ÷{ú·@w$XÀî[wEh€ãpçe¸S XØâ °•”\‰+>7`@PÂzNÐÀ[ (’L7>ù a°‰ d|AÀõ  üˆÄVn™AlEY’µ¦f“pÀz ²)"|&¼W °€€§¥NBP®©Ùˆ˜ €@€ ‚¡8[ŽC€™™O`§°a^h! *¥à)Äk„©‘ÆEŠ€gç zèˆ0”2@B©¨©*;³ÎV÷š &Awþh‹¬·hJTÂV¬Q‚°vâEUâ!áÁyn°±F„€V.øêÛ X‚rÐ&ú©¶?™•–NnKqˆšà0œR šÚ€ŸQ½‰„r^¡êÈHH¥˜bžI^¼ÆU,pùÁVb;2Zž ÑcC L° oǨN´f0`E{Œ´ J+ÌðRÖA±¢V/ЮDQ=æÄkfqÁ$”‚šÀxDÈÁ¿I¨y¦ €ù€˜D -Â` åÄ¥§SÆDüݲN­j„—ªÓÚ 9WIЋÄkí&UଓæD¨:¦ãDÌw9´†%Äþß¾ !8£¯íÝ·hÁnÂf³ž4æ“{M|ìcG¤ÜÞID¶´h+Á%Ð-¹ÊE0óbÊ`Â¥QñQ´ªö¨ØDȨ2Ziw\¾á—¿ÊôŠEÄÕ¹¬H€ùÓö yò“±o2ÂOmô' F'ö‚† ,2xG©Íb¶×= DÐÊBmÎóFæƒLžCæó &8/OHXÜ8`ê-,£ Àr= ®jô{Z`5­!,(bÒ2倸ýGzØÐ2#¼ ŠTÉשƒ#\%‘GÜ!®§º<„6‚°®'FáptùZKþM°Æü¡ï8ì)ÂUÈ5qL öáWÆ€%tÀxSBm®G„Šxû£ -´LKbH< Êt6˜ÀD²”ì5¹CßÁUHÀË¬Š°EÃÀ‚½‰bû*æÈIŽiü^‡ð®fpŒJ8ãk9 âQŽ…äãBJàœ1-0DB D£$ì‹`#‰ ·$Ð.pÄ›p 'Dõ¥-¨‚ªwÌÀ̧ˆÆÑeIS&nFQ5“ÐJ1Â=“œêLÀ:ÀÈó‹Ch̾9¼;þ¤›.*ŽB *<ÛÏ’JÔc.•‰²A! –vy„a‚™®þI-mîe`r3A òõ³(¬6Ðz—HSl9´cÒ™ x–Î3šÆ? ó©(MH±3Šéwt¨„~îÆEB½ã6…jdÈÞóJ€SF ¬"ØLM(Ádfi[» )×2QŽ"dy7¢Kµ"°ÁÅÔÎf hV³M¬€7ˆ_>a#0@B` ð,"h§I(÷G!ÎXÝÀ¥Ú)œËBàšmcEGÊõ|N*²¡mp³Qóu1†âŠ’%ÚVa¬YÂJ¤07 .ʪvµ&èSl"þì— • p+;å8À3Ý®]·à0‰ÞC àþŸ YÃuH¸•vFçTÛ9 @pY@íŽpX#ÐI !pÀ[â;q©IOò´2€”à|*âÀ£òÚ©“Åð$à¥Ò3ˆK´2°¹Ô¥¶†»íGw"‚ÇreVÓR‘ØŠÆ"ü×Iëyb~÷k€þ²¸®&À“œ„¿¥ –Gãb’†Fc¼֤ÈÁG€yʵáòhôü:‡GЯñógÝGgÆÐݱôj$O_FÔ“ÑtvþT}ø®FÖ©±ui\]_µØvt”}ìhçÃÙͱö´»Ým'GÜßNw8Ì]w¯»ÞÕwpô}ï€'Ãß½1øÀÞ …çFâÏx,,^o¼ä¥ylT~ò˜gÂå­±ùÌ{Þ§Fè?OúÑ{å¤OýLÿrÔ«þõV`=4dûÃÓÞ·¯=às¯t×ëþ÷Màý2„|·Ÿê¾/¾òAŸ|«7ùÐ?>2¤}LSßׯþŸ³O îkŸÈÞFø¿ÏÑñÃüäW!ú}±þô·«ý¼€¿û‘#]Ôþ8¹?.ôÿšðßÿ×2€´@€ø( x€,±þ€°à€ ˜è &Q¬€8¨ ¸ ñ¨ ‚ Ø$h 'X‚‘‚¤À‚*x.( 1ø‚1ƒ `ƒ48è ;˜ƒуœ„>¸B¨ E8„q„˜ „HXLh OØ„…”@…RøV( Yx…ü°…à…\˜`èc†öP†Œ€†f8j¨m¸†B÷|`'‡p(joˆwX‡Îs{¨‡o—‡†ˆ~hvtHˆ|8ˆÖWˆç ˆˆXŒ8؈x§ˆlG‰’X~–(w™x‰ê·‰“xˆœ~ž‘ŠÝPŠ€€Š¦y£èw­¸ŠO¡Š~ ‹°Èy¯þHx·X‹þ—‹ŠÇ‹º8€¾ÈŠ ø‹cC‹jŒÄèƸ˘Œ¸‡Œ–Ψ͘Õ8Êpw Øx ÜXߨÝ'¶8Œâxá8éxŽçGŽÕ°ŽìÈ~î(zóvWöh‚úØzæ¸1o y ÙYØ³Ç i ¹ùèÏø¹™™‘h‘ÍБé Ù{9’'!’f ’(‰‚%9|/Ù’ Á’‚“2™„69}9y“N¸“Øç“<9…@9Ž'”!8”Ã@“FɃH)~M¹”û ”b •Pi„OÙŽEY•A•`À•Z …þWù ^ù•U–ò˜•d ƒfÙ c™–‘Ж\—nI†ku9—íЖ%‘ I mTCr‰—‹Ð–" *Cm³¤ °j p=ƒI˜‰0˜ã£˜Tˆ‘)™‡0˜$ÀDD昙šYœé—H #x! S'‡–£ùƒw i™ "ðð¬)š¯Iv± ³é" hEÀ›½™Š¿ù ÁéUœÉyœ¤øœhû–ÍIƒ ° ÐuÐÙQÜÉt ËÉÃùAÆùÇèšÅÐ # Àà7·µÀ€ºéœì©ž—o4tñ7¤ð5ÿ4þTÒéŸÛžSà  º ºý¡BY¡§8¡ZŽñ¡ªŽª€!ú¡§‡¡½h¢$•#Z‘(š¢b¸¢¯à¡.ê2ú5:£ £¨£8Š|-š 7Ú£lÁ£ù£B*AªyDz¤DÙ¡KʤNi¤×¤P:•Oš TZ¥]y¥#È¥Zj—RÊ¡_JYše:¦«ç¥.¦hºˆjZ gÚ¦Ž÷¦-H§r: qÊ|lz§ã§üɧXh§2(¨€º£{: ~Z¨P¨C¨Šº ŒÚšª¢‡Z¢“ª‘ª •z©@ꨟ©œšsžÊ”›ªbú jªØ9ªªª*©qXª¯þj©±:«lȪAˆ«¶š«²z‘»ê¤½’ºú«™©®úªÆ:¬Äz ɬËê£Àú¬y©¬KH­Òª…Ö –Îz­ÅЬÜ:‡ÛšÙú­à­äê¦á ­çZ‰éª“íº®`­ðª‰ïú“õ:¯û7®ey¯ø €ú: ǪªæÚ¯®È¯Iù¯ë›¥ [°òÚ°º°Á°¦:°Û û–{±ëù°Û©‹•‹±!+–;²x`±(ûŽ'K—%»²pÚ²å*³0+*[³þè±8›³µº³ˆJ³… ´>Ë7;´&©³F{´=›´¾º´L«´¨*´O»’R‹‡U;µcP´XÛ­þWˆ]»µ_ µ`˰N;¶\û²lùµf›¦h¯kk¯Hû¶"[¶r;·QÛ¶uë²q›·n{·|K¶~û·vËtj+¸”W¸ k¸&‹·ö‡¸Šëbû¸º·’;¹t[¹µ%€   Vʸ˜›¸¿@ÂrÞã'Cµ¢;ºÈÙº†àÞƒrg5 »®Ûë(-ƒrL¬›»é¸O2>ÞÓÀ[ªëX.Pä=`»YK¼Â»ª¸K0>!Pj¡{½™k½ƒÔ—qS7§ÙK¾Ú¾„°° àaмœ¹î‹¥æ«»ý{½ëè~q>IaþO÷û¿Â»Žà ìUðt»û+¢ð{$%L°—}I›B0^8÷§̢ǰÔÛ°jÂÁóâdÐ#³‚¿—‘¿ûYc†zÂ!l¨ÆP PÂâå™ gØÖ¨¼Ã‰ÀI°°N¬+Å5œpjvÓºŽMŒ\ŒÂ&·åYﲺXÓ[FŒÄá/Žâ¡°Ù®ã^ã>N®8^Ón£;îâŸ}ä´MãÚmãséäàm¥CåEþ­TŽÞVä=ŽÚ<~ÀWNÞYέ[žä»ä'þåq(ã .ä^ÞäR^“nþàPNælµu~áwÎÞe~­gÞçôýçÒèpæ—mè]Žè‘­èbç‰>çÕ»ç~èL鄾 @Îèq=æ~žç„Ké%nék.ç™n•¢¾â‹þ~é.éVšê9¾ê¥Žé`Îêé­~êŪ۶¾ÐjNä >­°Žä‚¾ßºþ«Ž¾¥ž>èÁî|Ãþä¤ì¦^볎ÎËnì;›ÞëqùëXžísøìUþèœ.ØÉní¸¾Øç~ëå~×ëîë×>ãǾ«ïÞíñþæàžÛ^íìÎíj]ïizïv>ï¶ ðZÐâünî®>Ôéï ïî _ÖðßÕŸ/í´ÞæñÞŽçÓÎñíNñ#oñÎâÎåäîï&Oð•°ïî%Ñ?§Ïç.¬'×)æÁ÷ñŸòzÞñ6OôJ½ó¾ÞóÅ.ïùNˆJíßÃ=lþÅ­ÞHßíO/ë1×ÌÍÓWŸ¦Y¿ò õÔ]ö#mÝ+ýõÏëcOò,ïñ7_éiŸóËö[ïð3_ôyŸ°5¯Ü@Ïìb÷¬­ööÊnôÑ÷£>÷Ô.ønßök¯øªîõt­†/óo¯÷™Ï÷„?§—ßï÷ˆ/Þï÷Ÿ÷›Ÿø£oõ•¯±§oï«ÿ í”ßøQø{¯ú¹Oú­ÿ…vû˜úš/üœßûóÀúŽù±?â¥ݯÿøË/ú»Ïú¶ò¸ŸúÜ=ûL_û"¯ýTñÉúÄÝÏ¿ýÑÏüÕ/û’ëÞO %þa ¶Ia!˜¿ýÁ?ýÃÏþÿÅo @°h>ÀÆ”Tš4 è•–&€bÑn¹]¯¥UÉe³ùZù®¿áñW¦Ùõ­[ž×ïù}ÿ0Pp°Ðð1Qq‘‘ìê­1Rrò¬ãHia ¬ ²ì*Ë®¯‘.”m”±Ô´MŒÒõ6Vv–¶ÖöÖ±w—×äA`‰àƒlC ku uQU™‹YÑùY+º÷;[{›»ûðÑ;\ñà`) ƒ¬i@ !$”úî¸@mž¾Sú>__`@ Î`Â8,:|È0 ¼%ŸüU«ÇŸ?kˆ¦Q©eJ•+YBØ¥†JF„#"@ƒþ*;žtÙ¯çÇD%Ÿù„yiR¥_.ˆ œ’sä0¶1YÐ}CŠJ²k>£NÉ–5{–ÔW´ÙXbl+«;Hp7¼UcUÆWß½j×6|ñ‹ Œø–%˜4ÅÁ©«Þy€ 6Åù`ØÍ!—6}ºeSÔ¸„hðЉ/#’,ÀÀÁŒçdõ÷›h“¤û /J|urå˳©f.« LNb„¶‰à@D¼Ž`w6þ·|èbÓ?wÿ¾=àñÅñüÉs(ÐòÛÙOÀ„ù»±½ù éO”öü`ð œÂ÷ ¬þGS—‘YáÃMLîÂqÑp8;o0Í[oCm¼qµqœ…ÅãdTo¼ÿúÑ !w<É„tLÒ•Ñû‘??q"»0’É,µÜfÉ-ÓÒ¬Eü¤ÔJ=¬„¦L/Õ\S–.Ù LŤ1Ì´è”óÍ<õäÅÍ=‹‹óÉ9ƒLSŽ3ÿñÑD›$TÑ3œŒQP­"½¯ÑJ-ý ÊK õ-ÁI;µsÐL5UÓ>IãÑ)E qLÿ%°ÕW=•V?M­ÕNkUR^)ÅØSo ¶7@!õõÓTbpVbMrXgS%³Ù< õ¨ÚMï ôÙn–Øi]ÍþÕe?$Ömõ–Ý<Á V\Y=ÝUYuUm_6ß6^fç­³ÞPóxË}qí÷Üñ ¸W‚>ÒàZQanþõáŒUŒ˜Ö‰‹|ÕQs)F–^MÆcauØ{©­xÝ“e60eR=¾dd\æ{gþYÀšG½MtCÞY^’Yši÷„.uå…[Xi©›¾Z¹§/%úЋ“ºa¬ÅFMkK¹Æ¶ç—«¶xì¶!+»Ò³É3Zgª½.Ùí¼×‚»Q¹±4SäéÎiõ>-¾õ;gÂícÄ%WJño£f¤Ç3{òÎaªQÆÏ¥ð„׎ÙóÔUÝÖËQçþªô‘ï^ZõÚ b}OÑÓæùtŸmÿ½ ÜõÔ½wµg·øäÃÞ]×}‡üëÍ#Wžz.ßÚyã¡ÇvÍ«ÿþæß$þxÌ»çüôW¼Þììy/ÿu°b\ýúw__÷“†ÿy飷€°Àߚȷ=ÚÉÏ{T $¨¦ú{Dß)(Ÿ™=ð|Ó“ +ØAL]P†ãŸö x@žðE£kŸ±ú—Áÿmð…(”áƒØ·šn„ï3 òfØÃ*Õ°o7”ÝÍÃú‰°R¡ YHBñ'óÃÙ“X¿z ƒF4aXE/b…Š‹"ýЧÃòð‹i¼bÁÆ8ÅÝíþˆñKãר¥,GŠE{£çØÇ:féŽ\œ »ØÇ$þ‘I\PàÜXF8’Ž@cÍøÄ-.2]ƒd$Ãh9J>Ò’h$7ÙCDB«zt$ÏXÄRšR’ž,$&gÙ!Fªò•U<%’iËL¢m•8Ì%w ±Tj²•re ‡yÂbÅùåÜö(ÌfÎð™8Šæ‹¦ù7kÝ™×Da6o´ÍÕRšègÉi#siË„â:ØÎ˜9¥EIOÚóDïŒR7cy-júsœ± >ªOV†Ò•e§B[÷ɇò3¢´Œ§DíP ”†í¤A½ÉQÕy´þD 8óÇš€(ECKZ.‘Vsˆ0h'jQkBT™%¥NÁ'Ó ©”U7 fN‰jEпަeiC]êĦ¦Ï¨Bê•êП^µ¨O^TG:Õš¦K`µ]V'´UÀuµª•TkõØJ3²â”Œ^e*"J ¿þ° ,æjCžV4­ðª/%<²‘•ìd#{ØÂ¨®rë7ášÌ—‡íhI[ZÓV€b½l|2´».5¯qÝ'!7ºZªv|¯Õkl=kUÚ.Ö¶l´ìX}º×ß2S¨ÈelmƒkGÜæ¯¸¼êgåyÉå·¹‰|.u+Û‹wžþ×Unvµ;ÜæE·‘»M¯w¿ ^ë’•æÍ-zqÉ^ãV·Ÿâ /|y¹ÝÛÒ7œ ª~ßËÍÆò÷žò…îWÚÙéú¿E°1Ì]·´·ruo~ÓÉÜ —Ó¿Xìn†gáw»N0/LU×ÄÔMî~U¼b™mV[ NªŽ¹Êãk6ÄllñYæcÎ9ÇþqJƒìÜ!KÕÁ–19¼d&Wø¿H¶©–ѪdÅŽ×Ê3ŲˆŸ\Ö(oXÂ(s˜µÚd@ŽøÁŽ1„g\`6·ÕÍÚ-3^×ûâöÎYÎw3‹¹\d/´Ðu;´ ÔZÌÂYÊt¦rš ¼hF³6ϨþÜ3lûç׹ʗ~ô˜…œhÇYzǨEÍG7Òh>q¥Süe·š>¯Îu¬ýéIÏúœ¬Æ5Š2ÝßM«·¾~¾/¨)=løèÓǶ¯t#è_OÙÙ˜&µ“MMºnÓå̽†q¶w½mæD#Õ9 ~x WÒV6µemZ¯™Ü®.öjZS„K¡À<À€À›.òöô¸¯-i5ß:ßËöQ,d¢ ÐEÀ a'¹Öˆ›ÅÅýg´\S(oŸ¹•Ó–` ƒ 0À00›„+áfvÌfRo‡—¥ç£ :öXžœt\*N ƒ;–` „ ç½ÑþyÃu8tªûÚ,V·”ÖƒXôÕ0Ä!!C °„À:Pÿø–×ÞeŒq=Ø!W ÜE÷…z52 »M”P²›í7o»¡åÞàÇÙ=¤QâýÄø±âý4ä0G¨R…`& N¯ÂÙ@Ïô¡=&p„¿z`©WýêYßzÕ{à¯Æ˜Àèi?úÒoàô®×ýîaïWÙ×øŸ¿ý_°{ã³¾÷Rø}ð?|¿¢þøÑO¾ }ãw°Õ·¾î§‚lßøÝ×>øYýÜ“ßõâGú³¿þÖw¿øî_½úåÿzÀv þö?þéÏÿØÃ¼R Ë„ª@æhÎþæÞ‚²Ð#P'+Ð/3P7;Ð?CPGKÐO#PåüD2(#ã6În H`¢@°nsPw{ЃP‡‹Ð “P — ›Ð Ÿ £P ë6€.e(ï5þ­-²#à®àà#Ž 'ÝàÒÎÐØm:Þ­ ãPçëÐï°ÎPÝ*åì$ ò £"@@v" s"<Ø$±•` #kæØÄ€;€:±1_úÍVPO<"lð0OD@À.` pâÝ8@à®pMPþQ€• - @ xCM2à8à. BÀÝ<@Û¥kcLÑOD1í*…lq @k ô¤o1 À-üdM R1³1 p¢ñÅå”@þ1Înåûîï0!ßäq+%ý®ìô1_$/é*/QDq:`ÆQúÑD– 0O ÒÄQ`  óÄïtB"ÓA *ŸEïhBì'*B žŽ­1 è±ïj.;Ø#3<`"¨ã$פ` lR p_ `önTH`¢â!iÒŒ2 ’ ñ „ïÞDþ&r*ï(à#Ÿ*ÌAéF (uR*G’"-ò*Á± Àô¤+ùîH2 LÒÿ±PSÚb-×"r ð"¥r.ëòM.@1M kÃXšÑ 24Å)¡r&嵑½ñ1_N/ÕÄ1ñÕ1"5ÛÅ*\ÃêÑO@8à^2&¥8  €8€ pb: bñMv³ à7K`~Ñ: "'“Mjî£àЉÓ8ó%:¦c†±@ £"+0»cóä<%kH  = @(I€²æ@9ÿ@T@”@ Ôþ@ATA”AÔAB#TB'”B+”M6€;  $«à:ÀBG” H à:G <ûÆr.€Dc ¨Ò ðl´ pT èRF{”ñ± +•À;` €ä>`@š '7ºc AC ; @I½5%.€>;@"+¯+C`(àdQ²–ÒGÛtD  0BÀ1• I=`CЫ£0skîˆ3J5B  'áM < -R"`pC"6Ó. 7F­àDwÑMCõ¤1 †4 0  S‡ra2<þb Íñ;Âñß’À1Dô: <$r#òßîR pG;UT‹•HÕê4 @Mf‚é”À1iƒ’€ °ñ:Ôr ¢ð@"Ûq8± d‘S]eÁ(MÕÀN €MÏU 4Ž6jn?3Ò//®WÝÒK[ñ,»r4 ä5] v< ¬ƒ]-q[‘N‚Î1ÃCãÌï¯Â1_¹U D jÂ_/U,•@`!“X –dY6èà5 XÕZ¹ƒ6úµ ı nmŽc•`LKõc“À²C³dƒ> auÖNSéú”þô<³£æ 78 nñ1AÕQ§RW2 ,µT `ÇÕVûxQhÏöUYÛ5eÙt*£T" æ°ñò®jmUK—´K¹ÖW=6l³#¢ÜmWÓq7! €à8`C²²Ôq/× "€>ÑðCå€q1tCWtG—tK×tOuSWuW—u[×u_vcWvg—vk×vowsWww—w{×wxƒWx‡—x‹÷Â!ùd, X… 1"""+++444<<<MiCCCLLLTTT]]]bbblllttt}}}ƒ¤Íÿ‚‚‚‹‹‹”””¤¤¤«««´´´¼¼¼ÃÃÃÍÍÍÔÔÔÞÞÞâââëëëôôôÿÿÿþ@“pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´zÍn»ßð¸|N¯Ûïø¼~Ïïûÿ€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›´Dx D'þÏžŧ%£i ‚ÔMSœ5˜J•* ° Ã`@‰@H›"±SÊS4 k ˆ&S "ÉÛ2 0m{æí_¨C\X,añ –D0‚„$ €àèû§„ˆ»&šš>›š0Ó¨ÓLÀ B„Ð80Ékdol#‡ÇüÓ Ûâ@‘#Öˆ€×$v– 2]Hõ!‚~¹.&øñ?Ù „ Â!€Kx)àw‘ïi†'GŸørŽ!(+)!@ðqBb ðkCd`ÀV Ô ,5_kÅéD”O!þ0gá“YðASN8Dþµæ‰Z-0 ˆu@ˆHèÀxEHewF¨Ga{~íB"€ã ð¡pçÓ%ð\‚ VV‚|1 V•Ŧ!Œ  2¸¥…ÙUÀ’yÀ•÷I„Jt@%t€Aè)q$±‚ f€„T8A”Åvgep—^°^‹k¹– °ÀeæÙš“DðM}€¦¶È ð¨h!X àvÚ±åyJôz{¿1k­¤¾ùeD`¨×±*»~¸ä¤ÑZÊe}&D×è­ ¦Óµþ8`Þ¶ÐVz©ÜicãY[k¶,YçCü™DvLKÕ¡E` X(W„ ºÁ@e@l6 „«°VBs-fZÄ·„©òT  Õé„§}BxL¡…­:½BÀ$6ñ˜¢GðZhS]ИC›—,P{ZÂXÓ}°‚ ÈŒ4¬&è™ kÌ1Ë©01„X3Õ^íªZ «kSƒ½À¾m w`%„ðØÛD@•’Kt€k@ŒÄdtšét8;Æ¿ú1§#ŸN(Ѹ@ À]}®R-‹ƒµh[pßFL«/“匄þ›núZdæCð çæDäG¹é& €Ã9åÅÝ™øâ§«ýñð&Oä=5Z„NÀ 7DÍ%nĶ[·W DÀÁ´Æ'Á%¬ñ¡=´Í`Âá[ç|Ë’Aú…"ûɹz”´æ]k.ÈĦ¿|ÌHpÝá#4¡I5BZ_ŠÄ:!ÈÍf¡!²°¡9B˜ÖoJ°¿. ¡9_cê×¾B{¶Â5 ØÐNÚžCò³$Lƚό0‚±0`ŸF;b±Hý‰œè‚‡¼ ¹PC Q¸ºÉ fŒBÕzŠH„Çp®gRsS þ2(ß$-h¬Z[®x ºpI! ?³¦°§W´CÏ[Ph‚ia±‹"bq(D†Üi‹GÊ…0™ŸaZˆ:B À€÷ÁoH!uZè* ! P:h¹•ì^le`ÙC¨1VHÈÎGøº"Ð1x!p~9©€0™Cå[y??Z‡‡^ )›§Fª’-4Œ$ƒXI…ð'wÔO`*#he›UkØJ™å%AzÇB~ƒ=–ɲy©#¯Þ(Îâäý¸|IËÿ-sGFˆ‹EÉõX“‰¢áŽî¢§ÄH®­›”ÄçÅÂé{æH’Ô)ÖZþˆÇë³¢å<ˆÜ6É !jKÔSæDÊìÐra BhÚ;¯ÙÔ%”Ó2fд¨cAM ¢³–þñS4ÁÒÊM—看$‡ÀÌ:Ù1ÀQËC,ŠêÅ_GPÙ-µÊ´•2©òä")y69•k!ðš2“êÅ`¤h³ÎÖôGËiÂ4¦éÞ[3‚½,àV¹V÷U±§\IøÀ~3TSnÀ<µÈ2nIë]DTe P€°@²>ŸB—ºØÙ˧Ȩm…z R “ŒmpÓÎÙ=vtp]&­.»[SM†RÓ2\aM@M¸Ö—*$ÛN«þ\Õ²¶yyd$G± Û!4ÇÏ^!Ëw+ °Hà­ŽDD²Š@ˆEùÕ'hü&€DI £5 ·«Ãî¶Î» ð޾ëýï|ð»9øÂÛðä@¼áÅ‹ÃñŒ¼ ÊKþòd°¼74ùÎ{óܽçGÑkÃô¤O½P Ö«þõLp½5dûÚöÔÀ½íw¯{iô~÷°ÿ}Ñ}üâ—žøcG¾ñ—¿zå·ÝùÌ~ì¡wêKÿú··~ßµýî ÿßïþâÃuî‹_úägFúϯ÷õ‡Ýüì_¾û“1ÿø³ºþk‡¿ýcôÿZöÄ €S( X€B”€ÀÀþ€ ¸/è ø€Ë1¼`ˆ¨ ˜5Ѹ‚("h %8‚/q‚´ ‚(È,( /Ø‚)ƒ°@ƒ2h6è 9xƒ#±ƒ¬àƒ<@¨ C„Q„¨€„F¨Jh M¸„ñ„¤ …PHT( WX…‘… À…Zè^è aø… 1†œ`†dˆh¨ k˜†ц˜‡n(rh u8‡ÿp‡” ‡xÈ|( ؇ùˆ@ˆ‚h†è‰xˆó°ˆŒàˆŒ˜uú7‰Éwtχ‰–Xk•˜¸‰Õ§‰¡Š÷‰‡`Ф8x“øx«˜ŠÛƒŠ…‹®ÈŠ¢˜²þ8‹•׊¹X‹¸h€ºø ·Ø‹ÝŒ@ŒÂxz¿¸yÉxŒ6aŒàŒÌ8{ËzÓ$XÈÈ‹ÖX'ÐØݸÑð{ Žàè ä˜çXŽËŽwÀŽêˆ îXñøŽÅ0s`ôˆ€Ø˜ ø˜ ¸­þXýØx9=xÕP¹ éÙ!¨¹G‘y„é{y‘L¸‘çéÉ#’0è‘à‡’&i…*Y~ ¹’*Q’“×’0 2™7Y“«“gÀ“:™„4¹ŽAù“e8”ôg”D©†H™/™” Ù””¸”NI>YU9•¡p•c •X)†RYþ_Ù•y–ÃÀ•b¹ f iy–qH–ú•lÙ‘pk—•P—]€—v ˆn z¹—…Ø—ÿ8—€É’„ Œ‚Y˜òð—ZÀ˜Š¹xY*+síV¡ä˜é‰‰) "P+ƒnB ¿†7^Ò™›ÙŒÉB¢iRÁ‹š¹š†À˜$Ð?–©}³I›„`›•iR±vTB°›¼¹wª9 ·ùš ð ÄiœÇ ¾ùš×ci©y˜Õ™×ù«µÝ¹¶§áI‚ О @wã™óäž4t Ëé" ê™œñ•Ü) ÀRÑÀŒã/ €þà âÙŸQÈŸŸpHUqƒÒò!ð"Ù÷ŸJ‡š Ôù¡x0¢Íç¡$:–(š)j˜ñ`¢-z!:‘+£ú£P€£6ú:ê=º£$9£·ð£@:“5GZ¤ô@¤KÀ¤JjNšQú¤[)¤&h¥Tš’I*[𥗸¢Xê¥ê¦³0¥búydz’]z¦£(‰kʦÛ÷¦)§pZfÚ¡uJ•i wš§WЧûI§~J—{Zƒ…:¨¿¨&s¨ˆÚ ŠZœŒÚ¨©?H©’J£`*¨—º Úsšº©üh©D(ª Z¦¤ ”ŸZª\š©ª:ˆ§z ÚªOþ÷ªNH«²º“¶:…¹z«¨Êª¼º¤»Š…Áú«ºšªcj¬Äê’¾š¬nº¬Ìú¥Íú¬ïЩ±š¬Ô:¬Òê•Èú~Ûš­þ‡­ŸP­Äz­Ýê­®Új®™è¬êzäÚ®m:­è ¯w9¯gh¯ôÊ—åÊ”ùê®øÊ†ÿÚ¯Š°m¹¯Ë{ âú«ïz°þÉ®«Œ –±µÐ°‹˜[– ›±±Ø±õº±k¨"ë— ;²Åx²“°°¼Š±(Ë¢Ñú²0+¯%+³£Z³‰ª²6;Ž: ,{«.»³ Ù³‰³B[ A{´s ±Jû‘LÛ´æH´ð³²š´PµFë¨R{µþ™µ¸µ\ ‘`ûˆc¶k`µf{”^;©k›¶û´nû­m+¢e·PZ·œ9·v»²x‹TÛªh»·«·CÚ·‚«–†[›‰{¸y¹¸½é¸ŒÛ˜‹œ„¹d[¹;¹–k»¹_‹¹+¨¹ž;;ºt º¦Šº¦;¥»ºWªº|*º®ë£²ë«ªýXРРL™W»³Û¤Â»$0-~c?jÁ »ÃëÅ«5b?§êּϫµÎëðFç‡Õ“Ñ›½ê `†T×á{·ÛK¾%:¾+w×[¥í뾇¿GpÐ, ¹&·[þªøXÁ`s#¾÷‹¿tPà ÐðIÀúë¾­ëÀ•ÚÀ¸ÊÁܵÀÁ"J!caPÀ Šà+ì«Uyd¿üºÆ@fÂ’I™ØÉ_NW¾3¬¦Ç°õ «sIIpsd†ç¨?ºüàû·yž§_7Wq‹úÄMl¤ÆP 0Äû…›F€h3](¼©ø¸°lL+ŽÓSœ€FšÆÄ[»ZlkŒ|¼ÀsJpŸH0Ó#hšÇwÌ¾Ø ÈG€3…Ìpg|©ß‰°kúÉ’3·Éà3çÉ€¼_æi(ê¦hûÉžîYÁ‰Ü óÙžõþ¹ ò&Åb\VLq>¼ÊÜ ÐËü˽ìÉM Z *U8§Ä.‡Éª—JÊtýU&ÅÄ̈šÁºœ•l$Àõ+¬0ŒÈÙ|ÂÛL# ”1YÄÀå¼ÁÀàìƒgº;Ä)Ãï|³ÀPºás`gæLÎùܸ-&âK8Åî<нú  ð3=ŠÌаzι£€=¼øºh ²h_ WmofÑ” GUå¼ÿVÐ p0Ów;ðÓ@ÔB=Ô@!-´ø˜¸áÞc•@ÕR=ÕT]ÕPþG½³F¼U ÕV=ÖUÕ6=ÐÙ'H‚cæ,ÖdýÖWÕ6‘"~Òn ×cmÖ.Ý©»ÍÑ(›†¸y­×e-×2‹°[:ÝÒ[؆=Õ|Ý×yÔ¢÷,¾ÙQ=Ù”·Àà¿<úÔœmÕžýÙ§ˆØb»Ùœ}Ú¨ý±gÝ…¤]ÚTíگͺªÝÂÚ‘mÛ·²±®³MÛRíÛ¿ýŒ¹¤¼mØÆ}ÜÐÜ^¹ÜzÝÜÎͳÐÝ »MÜ’Ü{­Ò ×Ô]ÝèÈÝ3ùÝoÞWPØæØ}íÝÚ]Ü 5-Þv,¯æMÖè͹OMÔüMÔF}ÝæêþÞïׂ 0àNßY ¦÷½×ñ >àùMÙþÞNŽàîÒ®ÝÞ|náäM›NÜ.á#¾š%NÛ'ž£®á+¾™-^Ú/þ1®âî­5ÞÚâ>ãÙã½ýã2¾ãÙJäÌmä:®à‡ÌàGnÁ@nâB®˜J>ÝL.âH.­WÞYä[þ¬]~Þ_NåaάcŽßeîâU^˜iîàžãZîäž åMnÎSÎægn­mÖQŽç®àonÚknã}¾—ƒ~Øqžç†¾çãzèmè)-ç`Nç‰^Û…îãŽÎ°NØ’Ž¦Œ®é–Þé“Þà„¾èŸ^Ý—þ¾Ý¨~ç‚NêhjêŠ.å©îܫ߭>çN~ëéE¾é- ë]ë[âºþêÀΗ²Žé¹^é».ì2ÜÎŽ¸¡þ룞ì>+ífNë®Nß¼žà€Þíâýí7N»Õ¾äظÐ^ìÚ®çÜ~ì޾ì¬þîÓïé>µíÞèö¾íÏžïKï¸Þïîþïvï Nì¿Mî×~ð÷žðâ®êòžÙ=ñÑ~îXð)\ñÇ'ð½ÞìþŽì?ò“®ñ^Îñhìñºï¢îOñ*_Ù îÔ®ð·Íð"_ð$/‰5_îM`ì/ó%Ïó'ó¯­óÏïïóHßò(Oæ3ŸÉþ,¯ß?ßðÿðNñìõj>õÍ\õUpñ1ŸñOÿÙJóCoëbá.oíjoòãÞö¤ûö÷F?÷`ï·vŸõxÏô=oßg?ö”.÷D¿õeßõƒOê}mì}°ió\/¹ó­´‘ô“oñûÝßœÿÓÿÝ´—/ñ‰/¹^ç O÷Íwõ;ø˜?ú#ú[ú§¾ô/´°oö™|²?ë–ú(Þ÷)Oûp/ù®ßݾãª/üwOükÿ²·¯ø¹õ‹ùÇãÉÿ÷µßúͲÏOúÓâß߯ݯù»ÏìÊï÷¶_ý´{ýÌoø¸_ü;þºþ0^þõÞû{ŸÚì¯ýîþýðo±@€LEã™T.™Mç•N©Õ¤bÑn¹]¯¥2´J…•ïù&Ž¡eô{«fÏéuûŸ×ïù}ÿ0ðIhMÐð‘ NÎÏͱ22M,1Ss“³Óó4tS´ÔÔjÑò²¯R•k²Àìµ+ö7Ww—·×÷ˆðWØ®b À€ci Ù™)µ6ŽT–vZëVÏuZ{ø<\|\8˜ü|iA ãã`CI@ä¤$ ;»z[-ox¾ˆaB… ¶ù×\‡wF ˆ÷ؾ~þXœµ F‚7„xeJ•§Ì­ö@Àf’0ë¨_?þ“u ªÚI§§¥Ÿ.‰5zT‘H¤¢8@†$òÐ Ä%•Ú ihÒkØÂ.5{íÊ–iE`pdD I C£Ñ' =ܑDž:ìQi ›‘¾mÄ B •\R¥™4DÈÞˆÌmÊ$Ÿ¼KrœÌò‘­;’:…ÒÑ¡"Ç“Ë4ÕädË5óˆRÆ.Í”Í*$³Æ*¿tH? =ôO2eP# •RN*”RÂꬴ‰FÏ|4NJĤÓPEåÒQ4R+­‘ÔÔVMmÓÕ%45UT=s4EcÝUIXy…ÏÖUUõWëJýÙ}MvÖ\âÎU8 4Ùjõ\ÙfÖÑZ µ\.±ýUþÛütÍôSg¹Ý4Üv¯—×r'|¶LV… ÖÝ|y„wWyñ<—Ñt·õR} ößXý•–àNïýöàˆûKØÕ…½È³ÂbÍ¥wç•dþ(nÕb[–Uà×¥5ä–‹ùU/!&Ö^š‡uçÓ`µdXNV"Ú‹ÖÊã9Îé”vµgjŽF·h†o­9éªÙZ:Ô¦9ZY݆©µì´°ÆTëž8j¡ÏF9m“×npÆ®´lŒ¡Mùc¯»›ï¢æ¦´î¡õiÛç·‰¦ºïÄ!ú{ÒÀ ñÝ"Wœr„_Ôq®žúæÊ=?çrD3×›]ÎñýuqB?ttÓgÞþo£SŸ}œÕ mýá‚sw˜öÞ}±ÝOÜm>ýÍØ¥öù]€çSxØ wZs•“Ÿ>—å÷l¾øç·&eê½ÅúkeÖ}ø×³Ÿüûô ßMì%ïü}âÕŸJÁ1ŸwçÑŸoúýïdî»CÐܽ¼ý‰zÜýbôµÝ9°|äKà‹·@Ñá‚úƒß§6 ~vd÷öÀF0 TagDx;–΄0DaWXà µ0x/ìÞ O¨AùÙˆÃ!ótØ5×Ið| â32Äëqs1Ü¡ÍÇD+}k W:X@îñŠaÄ¢ý.ØÀ&ñ‡\ÔžÙÄØFdþQM[äŸç˜F7®Ži’#aÖh·zÑñŽ Ì£¸ (½#¦0~U $É8B3ÊŠHL¤$©ÂAfi7êc#ߨÅÂU’‰—ÄR&‘dG>ö”å» y@)‚‘‡L¥ 9™ÃGNŠT#*gI¿U>‰”aÚd øÉ^Öð—L &vx)Ìfó{É\Ò2íäIè}1ŠÐ¤ 4{ÕJÙaӯ̦6ÈM Q“ żæ!iHNÿ™SYÞ<^,q9IDºs~ðÜ—<=NWÒ–ø|g-‰xˀ撒»¤@§§Ï¡ó>ÖÜ;ÏÈÐô9aüô"EeiÏvZÔ{ÝD3öÌjþ¤ÿ©†Hz7”úó›)Í'AŸhÐq"ôž 5¥Li·Ò µô;-¥.yÚ;Ÿ¨3i:%ÊÆ¢^”¦â³i8zSVô©Fjû4jLŽÖ“ƒ/Í*õŽ:±®®SœTdNÇŠº²Šì¬Më?×úѶºu«ZŒ«S¿zЫvô®xu¢T—Q±Î5¦Õê`¹:UºâÔ®u$ªb÷Öý$Õ ê”kUÕJÙÏYÖ<˜…ZaKºÐݵ©UíjW›Ïr•±zulb9ûØ¿‚•‹ÎÐínyÛÛØ¾V1 õŽhÑFZ—×!¨Àr™Û\ç>·È+p#Üî—mÈUªiÉ]êŽtºþzÜ«·+Ôîš—™ÞýîOÃKÈÙγ®XÕéd5©ÝõrǺø/'5ËWIJò½ïCÛ‹Éý³©äÍl‚ùËàߨ,ù}Ù HÛøv¾ …0xì^ûŽV½L=lm-¼á}vØÀïíg_­Ö7Ä&Ö/ŠGIa˜ø¶~uqŒe, ûØÆ,îlŽ[œa¶ö®4f¥Š7úß;¹É$Æ1’‘Zà3Ù«Pβ”ŸÌå(S™¥V^ò‹³Ëããš9¨ôóŒC†] 9¸ÂS6rd×|]13ÈZF«—·|aÜÖÙÎJÆ3–÷ìgÏYÀ§D3 óãà¸ùp‹^ðˆ ]dFOXÐÊþÌ3Ÿ ` KV×~ô5MèÍVzÈ;FïIÉ,ê“zš›FµmU­æóÚÚÕþu7Mí_NŸÚÓVôªsÝè]ŸSÖÁ>4¨\_IûÕ™Žu¯ bb¶Õ‘¾6´¯vìxR»Á”V¶¥‡knW×Ûû7‚ÅMäT#úÓç.£iÈ=ÆÛ>³¾å½zÿÆÞiw­Cíl~÷û(ÿ^MÀÐßjçÛÜ"Î6ÂQ¢p†ãÉ` `xàÍëŽs—g]bwÓšâÆ–öbÔŽ‰$¡À<À€€üTÇ6¾Ë|ðIO<å ±¸BÐc„õ(!˜ˆ9`ô†ÃùÆO.þÐÉM~”©cêê€KwZŽs€&HX€Ž€å¼àCMû­uªË¹(nǔܵ¾òÄ€é¯ @lŽp•#HçOyÔ¿<îpÒÙtv â'Åø Ú1n e’á Ñ/d”'¾„ŽW{³W"úC™>‡g d&_#”Àò˜×<ÚIÏöÚ§7X¨g5ÏÉ¡{>ù^|ªOKSž•$ à=E<2¯ <úÑ—þô)0w¤ÖÐþö¹ß}ïoß©eƨ_~ê[ØÿþúÙ~ÔŽßüñ‡>úS‹öß¿ûî¿üåú£6ûðOõïPï¯TËþpý€ïÏ0»/Õ¯¿o3P½Ïíï¹oGüT«Lð1PK°Åì&þ.쎀ÈÎÌîDŒ€z ƒP‡‹Ð “P — ›Ð Ÿ £P § «Ð ¯ ³P ·p³ÎOŠN=Ø ’.ì–. H`ìµÖ ÛÐ ßãPçëÐïóP÷ûÐÿQ‘ ÑYë6&E@<À ä8N$æf®æàƒADDäHÄή8ND>.KÑOSQe D2nó %óvKøŒþDÔ$ `EàEÔ¤ààuKÓÄ@E€Xt3z1\ZîÅ(C ?ÀµôD`ÜáŽà2>Ždn¹d»¾ÑŽ£îa/Ò¤€8àn®BÀã<` « Màè¥_ñPH  Àµâ"ôÄ Õ±€‹ ¹¤K€#R!‹à2\º®cðP2 å!_/öŒ®Õä$@")eìLö.O%Ã…ø^Ãø¥E$àù¤%M@r²" *Ò"Ò%YúÑM`/3†’ïPä(Á…õŒÀ ]/xþ"à.4À ð€R)M€$_Ý$(M 0ÀêRM ˆà,w«Å õ0…À5ü$(ï²ÒrMÖ ¾®*ÕDr’0MÀì òWZ£øsQ äò0Ë’*À(5óJ Àô¤1]6<ó*­$‹ÀCå8@3M‚r& &Õ²,a3ÜärÓRR= @6]å+"Tþ20É"M !2û!à};‰Ð€X0e!†Dà\‡¨(Öã‘ðÀTœˆ„NŒWD˜-‘^†ìý¸S`b °ŸlÜ+p@C>á„3®… X²ö¡ZÚF„ƒB@‚†B„gלù„æ–ö=T‚Ê aaIùÝe'fÀwÐùhÄ  ‘ÁuFhðš4À¬Ap)pœB0wA2'Ä|!µ€ Hð€cjv(å<S¢f8ãp€@¥‰…à)¼ÆàáhD^ŠñÚë;¯¾ºêdØm,`¥ íJ«Ž)ªi„þ~º+qD@©¥˜ªêkä`®¶¨ª¦\pã…ë+±åæéÐ(ÆláEh*ÝÃG‡„4º·G ÈÚŸ¡6§Y žÍhá¨DP8ßYié@Cà'€•.Ãxª`'g¸a‡@,¯A0Ä}„uHë3výû¯ÿ:&-P/Ç ×v@tK»Œ² À@È™ A²–€çÇó׎mse›pv˜‡ᵟÝÜP 8fWØ"XáqölbÞÒFt±p|„k\aév‡ÁÜŒ¹¦fË‚­œØ·‹Ðx‡ Ž+_ Q*þP3‚ûê,€·ÍášÑLÏ)¼]LÝáÌD ãCˆ†v‡µ›0€Bt>Äç¦ ¡iæCln{ôÓWÿ˜çϧ,íRï·BgG B @³á¬Ž¬â¯ÚŸ8äFDU‚ÿI ø4Ï€À(Þž—³î',ƒ`߈ ©ù GnÙ«ìt5-·™ ÈŽÀ;$dÇN iŽ#½ôGw,]˜2Ð̃BðŒh³—æ=)ž‰¡fÀŠ:œ!ƒ ”A qRZŸBˆöD»å¥‡F $f-‹JàÀ€:#pkxsºÊzÊ(ÐmPtÙ‹ PþŠ`! ž/U!‹ôFwÇ !)H¸Œþ’§8 ï<,B^Œ7œ*j'k,‚gz¸–Ë[eM°Ár“9¼‹ 0Y„66”&H¢Æî VEð„@(ȲµTB À€26¾èF› *qôé`8´ª¡òuÆ42Ó&? BŽÃa {W„D^3Xä5m)ÆSz6¤¦ ,9ÃphgLf¾“Äaá‡ÄAeWy×DÓ3ÜÓÛóìf Ô@æ$G‚ ô2 ÛK‚õB6£Ç É ÎQ†“PB®Ž0‚±èù0z³ïŒsvâ ’ˆþ4Â$x““I:Âp7„Î!RÐë =E9ѹá38EªhÉÓñ]¯|¸ãZTÉO‚„M¬ [Pó>" -[À»je˜Œi&4BÖ&$³'kmf ¨š :;3_ÚÐâL€À@¶ûŽiÄ22è<°›Ì¼fôˆ”ÕåÀy\*J$E΄ßé|šV³YªkùL"ÝÔúÓ!ŒÕî<ÏRE¹Y³]†­ã¬dc›JK €RбØ5cZšÉÄÆ3u«­Ø"€Û†yB7à˜Ø@€qù"¾ô5„ l Ê_óšÌUýª^±©qœ!èJžù8˜þ×ìv6µ¹ÍjÿH[öjôÚO¬\ƒ.nY•ì4Á¹8•/M:…¸»=nrmwÇü"¦¾Ì ¥ 6žÑ²– xÀ[Ѐ"Ë“¨‘)Á…ÍŠÖWvD x*0q…þ!Z‹ Ð,6} ›&ÀÀ„#P”¢E£¤Q–6¬ÎµèBÓ)Oº3$´å-k›K]Ö;[ãÙ ¹N (b=L%×evµ6–ÊòÙ–©Ä…ÆêÌo޽ä&-ѹv©S>Õüà+4ø€ÀM»`ðt+’o¡” }E¢Éð¡:¿¡Q{ ¬£`áQ†%8Ö ¾UþèÀÒÑpX@K­:j ï#€ôHð€Zå%¬ ~tyÍ*†­Ï!9ôF=-¹8Öm W¥›€Y2çÓÀN6LƒS"äí¨ÈV¶´Uò¸$P˜·.BC?‚n{ûÛà·¸ÇMîr›ûÜèN·º×Íîv»ûÝðŽ·¼çMïzÛûÞøÎ·¾÷Íï~Çûð-4œ«G\–º½DC£ÂÎð†;üá¸Ä'NñŠ[üâϸÆ7ÎñŽ{üã ¹ÈGNò’›|xˆ‹†s£¹RJ›Íã-s6àïšÛüæâFÎwÎó~ë¼ç@ú¼.ô¢ÝD#44€Æn€Ûâ#M þ¼þ4Févu96`] ®SÃëÒÀϰ¡q4F}:ÒpŸÅž °KÃíÑ€û3غ_CîÏÀ»3ôÎ »»Ãï]×ß™1xež‡ŸFÊ«±xj4>ìZGâ§MùAL>—¯¼æûùst~ó ¿ÃçË1úЛ>¥GêOÏú5¬>¯o½ìËûoÔ~ö¸ÿÂí»±ûÜû> ½ßFðOü) ?Ç/¾ò›ük4ùÐ?Âó«1ýè[?ëcG|ä¯Ïý+Tßï~ñà ò‹ß÷æŸûöÏÏ~'¤ßïo?ëãß÷õËÿþÒ·ÿ:èÿÍóßðú×ý÷É@€(mx x€À¶þ€Åà€ èh8 ¬UÁ€¸Jø ¸~ó½ ‚ h$¸ 'X‚O‘‚¹À‚*h.x 1ø‚31ƒµ`ƒ488 ;˜ƒ-у±„>¨Bø E8„'q„­ „HHL¸ OØ„!…©@…RèVx Yx…±…¥à…\ˆ`8 c†Q†¡€†f(jø m¸†ñ† ‡pÈt¸ wX‡ ‘‡™À‡zh~x ø‡1ˆ•`ˆ„ˆ8 ‹˜ˆýЈ‘‰Ž¨’ø•8‰÷p‰ ‰˜Hœ¸ŸØ‰ñЉ@Š¢øwˆy©xŠÊfŠ‡àŠ¬¨°X³‹žþ·Š·˜}¶ØŠ¸hµ¸‹äð‹‚ ŒÀ{½HzÇXŒüDŒ€ÀŒÊÈ{ɨzÑøŒ!8ƨ‹Ôø`ÎèÛ˜ØÐ|ŽÞH}Öâ8ŽàW޶§ŽèƒìØØŽësŽy@òïÈ öxõè°ü€þ˜‹ÙTYYÆsÀ Iù(|ùD8‘Èg‘™„é|™‘NØ‘ä8Yƒ I 9’#X’é(’(©ƒ* y,Ù’.q’p@“2‰ 6é9y“7ø’åç“¢ÅøÜÐ `à3p fùÐÍо°`]€~\Ñ!M–¤¶Ñiv±§†˜1o‹¶Ò–×ÒKpX% p7Ìq ÓaªÓèqàÓf9°ÔLÝÔNýÔ@D½·™´á.þZ6¿@ÐÕ^ýÕ`ÖPSM·4«v)€¼ÌÕbýÖaMÖA-ÔkZÖªauŽ•n ×|=Öv­¶;)0zÝ×}-×tͼsýÂëî‚k~¹Õ†Í×ˆØØ»Ø|àõbÔ7­{’=Ùo]Ù–MœÝM¾{ Ú`-Ú£íÁ˜½¼ÏŸÍÚ­]Úd ¯«mÛ]íÚ¯=Œ¸Mø±Û¼íÛ¿ÝŒÁ¤ÄmÛÆ}ÜܘÜr0ܼ}Û±ýÚº=Ý_ÝÜÎŽÐzµÝڽ݄ÜÝp»Ü¬Þâ×ݽMÞQ»ÞìÞX?ôÖÙt ßà ”øÔé-ÇjÞ -þßÞ·ÕP]àP-ÕÕ ´ø=ݾ©PìÝÞ Î³ ^Üúýàî×~³ÎÜáÞàBÝáçýá.â8Mânâ!îÞªâ“âÆ‡á-¾á2 ã†-ãR€ ß.nŸ8~Ø,îã6î²ANÙCžßEž²G×:ξ4Näý}ɽ à1žä þãîÙä¡å¾ä"ËåbýäPÀã'®åá)æqíåæ«æÔ=§=®äSçÙÍæ%îækç^Mæ<çY®çËç.çg.èKènè5^çhî”ßèŒ.åý­è~î~QNç•þèé_>éšžÞ–Žç+Žè ;êíjæþ¾é¦î Ò½ê‘ èŸÎêÿ}è .é´ž§Vžã¤~å­.°¨~ë³.êœ^ذîÙ²ÞæŽþë|ééÊžÁÉžçË^ëÇNÓÑ^êÓ®ë¶íÛNìÌŽ‡Î.íÜ^íÎìãNéÞNíèŽìÝ.Þæëí¾ÝïÎîä~Üóníñ^îÅ®Úù.™™Žëé®íõ~ ÿ>ìî¾ïZ½ëBžêÿì_åýNð×îëÙñÏ™ /îŠ ä ?ñ¼þíözï…šñØžë¿îø~ñ£Mòþò _ññó/óþÚñNÞë!óÚ§ó]þñ4ßòo–@?æ<ó(?ó,¼&OñKŸóCïôþ6¿ó"/¯./ñSŸØY_ó[ßEï¦G¿æBßô\ö7<öq~î¡~ðW°á~òðnö`ÿö«öw^ö*¯ïvo ¯¾÷/ÿõU@ß6Ûh?§xßçIóÌßaÛõÀ÷ô=ßÖ^ùLà†ß÷‹™ø…ÎöO´Oõ‚OªúC;ú^õA/쿵ªïà¬ôzßöXûUpú¶_ò³Oö™¯î»øtŸû’¯ô^‹û¦_üŒ?÷¨¯à‡ßÖ¥?㽿öÌü¢ÿü‘Íù‹îù¿òÍOáØïÙÚéH:ýyïúûáOÓã¿øVþr¯þš²í_ûŸïýÖŸúë_òõþÿP_ý@`‰Åa2-™Mç•N©Uë›Õn¹]ïÉeó™ J¢Ùm÷»ª¦éu RÙVWìýãZ€Ï¯î1Qq‘±ÑñRK-/²ÒMްmOÓÎ0pðÓ(ôò5Uu•µqÒ5V6“´”S°Ö–wTWÈTVx˜¸Øø8 yùªb À€#jÀúšÄ‰öøöÌ“»›÷;7üΛ9]}½ÝMÙ}A ãã`J@äD mæ˜ FÎׯƒfÀ…[bD‰×Á£h¬>& èC×d·‡dŠüH²œÃ“Y¶tùR‘E˜®hRíÓþjãTɳLI…+ÅÕ5rfR¥K™N‘ÙôÔMBÈðdß„xr(P” M–Ò¤X¨kÙ¶øÔí£ šŒ€á‰ >d€æ$AsçÔEöÝá²…ã6vü˜\ȉ ÐebïôŒÔ0ÙZ ÏŠ&EzrjÕ«ßHfÝÁT&!\¥ÂÀf`Ÿi›þ„ú‹ÑÑf_7~‹käd¸I÷@ ¨'Ø­ ¸ᧉW<œñrñãWo`pž‚îä¹dœ¶dAG*›;ëþ¼¾Ëößø¹èÇΟ½\K¹¹À­žæò1ážàÀ0øË3ƒ”ä;þîÂóηÿ64Ä_*PÄf  š÷ÀA"0 €p@„¯³ðÃü2Ü/Çþvô°Ä …„‰Ä!ák±Þîëq ÿÉÎÈ(¥¦È)q¹.IQ–TG+½ü’˜*ÁIð¸üéLÞÆ\“ÍKÄlS»ÑÔ²K:ç„Ï<ySÏ,ÊÔ0MÄìT³ÏB 5ŒÉC©ø“Ç@³ìäÇ'/T”ÒCù¬4 FTPH;”4QLEÅóÒQí«ÓÓ-9}ÔÔV+-ÕUMAuÔÌUkuWK'UNB{Q5UTs6OX[•Õ(}ü4Ù]¯p²ÙP‰6DcMEviŸ4ZZ¥Ü1­þ[PœµÚl½m4Üv­WÔrëP¶InÕµõ[wõ^L奃^ ™½7Ø;÷=¸ÚsÉíµÓ_…EX„%°ßWf⇲×\m'þØ­Š)ýw—u7-ØWU6NdEI."`?9žWáEg¸æ•u&2g‹±¼Õaƒ3zç¢kYןó :åîØè¨!CÚЗ‰ˆ9¹›KÆ—]©½n‹êB­þÃã8´†¹çLϾ:í¯Ý^&ì>ÇÇäY¹>ùí¼gŠ[Ϲ «»Û»íÖ›p—ø.öb ‡n:¨µÉ.r–'5ñ¥oørŒ#ß¼ÉáôëmŸ¦¹l›GǹtÎU?Åó6AoþÛÆÓ·FóÕm?¦u6_O]Št¡<ðÛ…7&÷5wœ`¦kžùVŠ·ò®•×¼qÙÑæ½ùìÁÞåèñÎ\q§#Öž|Öa¯ÚûÁ§¿úñË’çÁ<øäÁ·þü‘ÿKúi§~,îÓß÷t>±¥/xëÃ_û4F@nOeþS ôîGÁ^ÜKšØÀ ƒ!DTh?ñyLŽ£›Y˜B ¢Oid ?ȸÞPf/<` ¿wB¦ÐzlÓ!È?/I°‚=œ¡ ‰ØÄÞPn%üÝÿØWÃå9‹6bߤH:äMq‚JÌâ—`Äwuu_ôbýÀHF7šþÀŒSB¢™¾Ñ‰q”Ò—xE ÞDÔc”øhEþ…Äá THDþÐŽ‰Td É/4Î.ŒêK¢&'ÙÄJ É‘‘„diÙI~2H¡$¥$Áà»5š2Pä"9ÙÇCŠ²Ž°Ä *K¤ÊVªðol|¥.)KÄÑ2›L&ýHÌ òRD¾äP)ÄÇ9³˜[<&5§ÉÊnŽ›E4&åiB[Vñ‘¹ §þ ™°r¶‘™·\%8× ¿v‚Hšq²æ ©¸ÀzðžʧŽöÌ~ÊðŸâÔ&9¹©Ï†ô¡ m^@4ÐeE´^ DUGQŠ]òzjL£0GÊQz4þ@Í(FÆR“®¥ìQiK½éК¾Tx1%ÏLe¦Qcº²¤8ÍŸNÇÃÓ¬ùT›@ŤPßGTñUt.=ªT™Z8§.ªèæF͆ԪuœŸ©EºÔx¢ó«9 «ëÆz̓Š1ÍL«í®Šœ¬v•ªZõê\ÉW×ãÜÕtyÅëMùjյꮭüÌä2ÏéÏÂÂô°ÆK¬AkNC¢õ±›ó+Ë&ËÕÀv¥ ÕB RZÓžµ¨HfÙºP±¾s˜Ê´l\åùËkÜ·¹Õ­kY›šÍ°NÙêO‡›T‚T¹ÉUîr™[ D¶·Žùík‚ûĽ¾µ–—ult Ýùu–¸þ×­,<‹Pîv—·ˆ…mPeK^íš—r=o/½Û?ðW¼ííY·;_ú¦W²ë5kyáßÚ~SþÅg}xß²†”¤~oœ0COÀÆ.c'œ]Úb¶Âeð3LÖkx¼û%°‡C¼à ·ÄnM1{ù ßy&¸Å2±|box¶qŽuübûÆX±ú¥ñŠ9üáþ¹È$D2e•,a'Û¸š‚…²ow¼ÇÙ½W¦p–E»eêv™_ž±•…üdË×ÌXEsw§ìYáæ·Æc~óãü×9[²ÎáÕòmÝB÷9d¥š«ŒâFÿxÍŽFt”AV]µá™ÉAþ¾1œ'}f#78Ðø=4”*éL‡¹Ó~þ4‰G»VÓnrª9»j‡úÁ~´ŒwdZËÙÖ^ƵygošÏ¿žÌtYciR7×Å>µŠ• \E§’ÑÆž5²‡lSzV{jצï°ÁLm1›Ìß·tÅÍlO{ɲÖtºq¼îÆ0{5Îvu™§Êï¨úÛÞLÁ·jô ëW&ÖçÞvÀÙlÈ8Ò jxàÙ䎴®µ=ï=w›á`k7[æ‘ <¡À<À€`¼Bê¾(À+óÐÚ<äMx<ÜÄø@!€H9l¤ñ^S¹ã`\¼Q “¥Sêéºrþ¸cšÓ 'NX€š€żÞ3ÇyO#f¦›{&f§”Ú¥n¨}6VyWš˜§áÝg·Â“Çvn»¹%~7”à£8õÆÌ¥.wyB Є@EeÌ;ß›Nï†ÞÛa§æõÄyÊ>.•I|f˜PÆ;ò`ç4DiþYƒyþ暟ìáD{Ä‚Þ-R¡Jmž0€ú,ÁîNx¼(P|ãùɧÀðaZlúÑ—þô©}˜¶Pþö•Ïü 8¿úáÿõK›}îŸßøÞ7-Äßþé“ æGÿùÕ_Úç»ÿðÈýñßþœ–ÿú/üô@Ûðþ§ïÿÀo«/P©Ù¯¥/3ÐúN«8°7p±ë œƒ ®.뚀¸Ž ¼®E˜ tËosPw{ЃP‡‹Ð “P — ›Ð Ÿ £0£Oz>äà ‚.ë†î H`þ!µÂP Ç ËÐ Ï ÓP × ÛÐ ßãPçëÐïóP÷Së6  ¥ä<à¤9Z$å €å\®sÎw¦"E@Eàë–€E.î;Ñ?CQÁ-#.òòäñp ÷–"ç$@ Fþ FÀ¤à›Ào ¿ÄF€xd‘3j±]JÎNNO<à.ÀðV‹MD@à.  6ãâ8@åÑK¨Ñ™ÀÀ0¾¤ 8à^n ªb»±]¬Ð~®Oœñõ„ı @Vk ؤÇq ÊO¼ÑJ 2 —`3 2\ªnOPOoóä Kïô|®Á¤#ÉQ eëò#á#$§E÷fƒ÷šq+bdNFÒDà%— @¿Ä&M qq €!×Äô:'mC'y2\D ú‘ôH%øB î®&ýq 4²ôþdpM~20Àô"zrLÀM€+cð©Å`ô(…@6R+Õ2õØr}ò.Ÿàê¢L$à%×r ¼®(‡%6vO)À,¿ò.“² vò1§ä'ÏØD0I6“2+²$M€+¥9*S$ïÒô$ ²/1sM!— %ïÑLÓUì‘#*e.ë2+c ’ Y36àêÓJ¼à7%2’3\pC1/õÄ$€úÂ*±rM:€ö¡8€B; èL¸Ó;Á³F€= v²6ÁÄë†ñ> ëÆ³<à0‰ÅPDâ Þ|o™¢þn+.1Œ‘×ä@tH  A @lsH@·Ò€A‘q?DCTDG”DKÔDOESTEW”E[ÔE_FcTFg”FkT ¸1A\'© . lTHY¡àF @€´ösñ`HŸ3 0Ô ¨å² 2J·Êñ@0™@;à àé>à@2 fp F†‘$ÔâL MeÄ9q.€B;'5` 3Ðr q .¹”QA‚@ .`G— ÀL= 0²*¤¡8â½î¸N£ @,1n < 1Uþ'`"DìbA(Õ.@BÀIá(†QRUXAµRMQz-½Ñ*d U³!@K-ñéÔ#H— 9j'C²G•ôR xuXËŠÕ(• î–,ƒî˜@0$è¢!27,±'áõò')R"OŽR‘1è‘\ÍaÑàà=ÂÔRÕõ`— èÄë6(C“6³‘÷ø”cÕÓ÷¡E&6aOV ÐU]—@›àNÔŽRéòPK[5–_™Àó38öV`d/ÑdQ–h»]Y!vQ›u R®MoRñ @!]²uE 5jÙd)Õ«¡EâµhÃv ö=VÖ ^6SäS´E¼"„"`-Òç€U]`5kMÀV5h[àÿÒQlw ŽöXÑÖ]U-áT"À뤱ŒÑ÷`ÎnÝOÓtOûö[Óh™€dãNÀquÙà$¹`uS×ueXŽ6d·àN_w]!(4 Ð Z7wƒWx‡—x‹×xy“Wy——y›×yŸz£Wz§—z«×z¯{³W{·—{»×{¿|ÃW|Ç—|Ë×|!";amp-0.6/docs/_static/nn.svg0000755000175000017500000012566113137634440015551 0ustar muammarmuammar image/svg+xml input layer hidden layer output layer input # 1 input # 2 input # 3 input # 4 output(energy) biases 11 (1) 31 (2) 41 (2) amp-0.6/docs/_static/amp-logo.svg0000644000175000017500000020312213137634440016633 0ustar muammarmuammar image/svg+xml amp-0.6/docs/_static/nodeplot-Pt.svg0000644000175000017500000065317413137634440017345 0ustar muammarmuammar amp-0.6/docs/index.rst0000644000175000017500000000405613137634440014617 0ustar muammarmuammar.. Amp documentation master file, created by sphinx-quickstart on Thu Jul 30 17:27:50 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Amp: Atomistic Machine-learning Package ======================================= Amp is an open-source package designed to easily bring machine-learning to atomistic calculations. This project is being developed at Brown University in the School of Engineering, primarily by **Andrew Peterson** and **Alireza Khorshidi**, and is released under the GNU General Public License. The latest stable release of Amp is version 0.6, released on July 31, 2017; see the :ref:`ReleaseNotes` page for a download link. Please see the project's `git repository `_ for the latest development version or a place to report an issue. You can read about Amp in the below paper; if you find this project useful, we would appreciate if you cite this work: Khorshidi & Peterson, "Amp: A modular approach to machine learning in atomistic simulations", *Computer Physics Communications* 207:310-324, 2016. |amp_paper| .. |amp_paper| raw:: html DOI:10.1016/j.cpc.2016.05.010 **News**: An amp-users mailing list has been started, for general discussions about the use and development of Amp. You can subscribe via listserv at: https://listserv.brown.edu/?A0=AMP-USERS **Manual**: .. toctree:: :maxdepth: 1 introduction.rst installation.rst useamp.rst community.rst theory.rst credits.rst releasenotes.rst examplescripts.rst analysis.rst building.rst moredescriptor.rst moremodel.rst gaussian.rst tensorflow.rst databases.rst develop.rst **Module autodocumentation**: .. toctree:: :maxdepth: 1 modules/main.rst modules/descriptor.rst modules/model.rst modules/regression.rst modules/utilities.rst modules/analysis.rst **Indices and tables** * :ref:`genindex` * :ref:`modindex` * :ref:`search` amp-0.6/docs/installation.rst0000644000175000017500000001473313137634440016214 0ustar muammarmuammar.. _install: ================================== Installation ================================== AMP is python-based and is designed to integrate closely with the `Atomic Simulation Environment `_ (ASE). In its most basic form, it has few requirements: * Python, version 2.7 is recommended (it also supports Python3). * ASE. * NumPy. * SciPy. To get more features, such as parallelization in training, a few more packages are recommended: * Pexpect (or pxssh) * ZMQ (or PyZMQ, the python version of ØMQ). Certain advanced modules may contain dependencies that will be noted when they are used; for example Tensorflow for the tflow module or matplotlib for the plotting modules. Basic installation instructions follow. ---------------------------------- Install ASE ---------------------------------- We always test against the latest version (svn checkout) of ASE, but slightly older versions (>=3.9) are likely to work as well. Follow the instructions at the `ASE `_ website. ASE itself depends upon python with the standard numeric and scientific packages. Verify that you have working versions of `NumPy `_ and `SciPy `_. We also recommend `matplotlib `_ in order to generate plots. ---------------------------------- Get the code ---------------------------------- The latest stable release of Amp is version 0.5, which is permanently available at `https://doi.org/10.5281/zenodo.322427 `_. If installing version 0.5, you should follow ignore the rest of this page and follow the instructions included with the download (see docs/installation.rst or look for v0.5 on `http://amp.readthedocs.io `_). We are constantly improving *Amp* and adding features, so depending on your needs it may be preferable to use the development version rather than "stable" releases. We run daily unit tests to try to make sure that our development code works as intended. We recommend checking out the latest version of the code via `the project's bitbucket page `_. If you use git, check out the code with:: $ cd ~/path/to/my/codes $ git clone git@bitbucket.org:andrewpeterson/amp.git where you should replace '~/path/to/my/codes' with wherever you would like the code to be located on your computer. If you do not use git, just download the code as a zip file from the project's `download `_ page, and extract it into '~/path/to/my/codes'. Please make sure that the folder '~/path/to/my/codes/amp' includes subdirectories 'amp', 'docs', 'tests', and 'tools'. ---------------------------------- Set the environment ---------------------------------- You need to let your python version know about the existence of the amp module. Add the following line to your '.bashrc' (or other appropriate spot), with the appropriate path substituted for '~/path/to/my/codes':: $ export PYTHONPATH=~/path/to/my/codes/amp:$PYTHONPATH You can check that this works by starting python and typing the below command, verifying that the location listed from the second command is where you expect:: >>> import amp >>> print(amp.__file__) See also the section on parallel processing for any issues that arise in making the environment work with Amp in parallel. --------------------------------------- Recommended step: Build fortran modules --------------------------------------- Amp works in pure python, however, it will be annoyingly slow unless the associated Fortran 90 modules are compiled to speed up several parts of the code. The compilation of the Fortran 90 code and integration with the python parts is accomplished with f2py, which is part of NumPy. A Fortran 90 compiler will also be necessary on the system; a reasonable open-source option is GNU Fortran, or gfortran. This compiler will generate Fortran modules (.mod). gfortran will also be used by f2py to generate extension module fmodules.so on Linux or fmodules.pyd on Windows. We have included a `Make` file that automatizes the building of Fortran modules. To use it, install `GNU Makefile `_ on your Linux distribution or macOS. For Python2, then simply do:: $ cd /amp/ $ make python2 For Python3:: $ cd /amp/ $ make python3 If you do not have the GNU Makefile installed, you can prepare the Fortran extension modules manually in the following steps: 1. Compile model Fortran subroutines inside the model and descriptor folders by:: $ cd /amp/model $ gfortran -c neuralnetwork.f90 $ cd ../descriptor $ gfortran -c cutoffs.f90 2. Move the modules "neuralnetwork.mod" and "cutoffs.mod" created in the last step, to the parent directory by:: $ cd .. $ mv model/neuralnetwork.mod . $ mv descriptor/cutoffs.mod . 3. Compile the model Fortran subroutines in companion with the descriptor and neuralnetwork subroutines by something like:: $ f2py -c -m fmodules model.f90 descriptor/cutoffs.f90 descriptor/gaussian.f90 descriptor/zernike.f90 model/neuralnetwork.f90 Note that for Python3, you need to use `f2py3` instead of `f2py`. or on a Windows machine by:: $ f2py -c -m fmodules model.f90 descriptor/cutoffs.f90 descriptor/gaussian.f90 descriptor/zernike.f90 model/neuralnetwork.f90 --fcompiler=gnu95 --compiler=mingw32 Note that if you update your code (e.g., with 'git pull origin master') and the fortran code changes but your version of fmodules.f90 is not updated, an exception will be raised telling you to re-compile your fortran modules. ---------------------------------- Recommended step: Run the tests ---------------------------------- We include tests in the package to ensure that it still runs as intended as we continue our development; we run these tests on the latest build every night to try to keep bugs out. It is a good idea to run these tests after you install the package to see if your installation is working. The tests are in the folder `tests`; they are designed to run with `nose `_. If you have nose and GNU Makefile installed, simply do:: $ make py2tests (for Python2) $ make py3tests (for Python3) Otherwise, if you have only nose installed (and not GNU Makefile), run the commands below:: $ mkdir /tmp/amptests $ cd /tmp/amptests $ nosetests ~/path/to/my/codes/amp/tests amp-0.6/docs/community.rst0000644000175000017500000000161013137634440015525 0ustar muammarmuammar.. _Community: ================================== Community ================================== ---------------------------------- Mailing list ---------------------------------- An amp-users listserv is available for general discussion, troubleshooting, suggestions, etc. It is available at https://listserv.brown.edu/?A0=AMP-USERS The archives of this list are also available to members of the list. ---------------------------------- Bugs and issues ---------------------------------- To report bugs, issues, works-in-progress, or feature requests (although those might best be first discussed on amp-users), please use our Issue Tracker on the repository page. It is available at https://bitbucket.org/andrewpeterson/amp/issues ---------------------------------- Contributions ---------------------------------- You are welcome to contribute to this project. See the :any:`Develop` page. amp-0.6/docs/introduction.rst0000644000175000017500000000222713137634440016227 0ustar muammarmuammar.. _introduction: ================================== Introduction ================================== Amp is an open-source package designed to easily bring machine-learning to atomistic calculations. This allows one to predict (or really, interpolate) calculations on the potential energy surface, by first building up a regression representation from a "training set" of atomic images. The Amp calculator works by first learning from any other calculator (usually quantum mechanical calculations) that can provide energy and forces as a function of atomic coordinates. Depending upon the model choice, the predictions from Amp can take place with arbitrary accuracy, approaching that of the original calculator. Amp is designed to integrate closely with the `Atomic Simulation Environment `_ (ASE). As such, the interface is in pure python, although several compute-heavy parts of the underlying codes also have fortran versions to accelerate the calculations. The close integration with ASE means that any calculator that works with ASE - including EMT, GPAW, DACAPO, VASP, NWChem, and Gaussian - can easily be used as the parent method. amp-0.6/docs/analysis.rst0000644000175000017500000000225313137634440015330 0ustar muammarmuammar.. _Analysis: ================================== Analysis ================================== ---------------------------------- Convergence plots ---------------------------------- You can use the tool called `amp-plotconvergence` to help you examine the output of an Amp log file. Run `amp-plotconvergence -h` for help at the command line. You can also access this tool as :func:`~amp.analysis.plot_convergence` from the :mod:`amp.analysis` module. .. image:: _static/convergence.svg :width: 600 px :align: center ---------------------------------- Other plots ---------------------------------- There are several other plotting tools within the :mod:`amp.analysis` module, including :func:`~amp.analysis.plot_parity` for making parity plots, :func:`~amp.analysis.plot_error` for making error plots, and :func:`~amp.analysis.plot_sensitivity` for examining the sensitivity of the model output to the model parameters. These modules should produce plots like below; in the order parity, error, and sensitivity from left to right. See the module autodocumentation for details. .. image:: _static/parity_error_sensitivity.svg :width: 1000 px :align: center amp-0.6/docs/useamp.rst0000644000175000017500000002625513137634440015007 0ustar muammarmuammar.. _UseAmp: ================================== Using Amp ================================== If you are familiar with ASE, the use of Amp should be intuitive. At its most basic, Amp behaves like any other ASE calculator, except that it has a key extra method, called `train`, which allows you to fit the calculator to a set of atomic images. This means you can use Amp as a substitute for an expensive calculator in any atomistic routine, such as molecular dynamics, global optimization, transition-state searches, normal-mode analyses, phonon analyses, etc. ---------------------------------- Basic use ---------------------------------- To use Amp, you need to specify a `descriptor` and a `model`. The below shows a basic example of training :class:`~amp.Amp` with :class:`~amp.descriptor.gaussian.Gaussian` descriptors and a :class:`~amp.model.neuralnetwork.NeuralNetwork` model---the Behler-Parinello scheme. .. code-block:: python from amp import Amp from amp.descriptor.gaussian import Gaussian from amp.model.neuralnetwork import NeuralNetwork calc = Amp(descriptor=Gaussian(), model=NeuralNetwork(), label='calc') calc.train(images='my-images.traj') After training is successful you can use your trained calculator just like any other ASE calculator (although you should be careful that you can only trust it within the trained regime). This will also result in the saving the calculator parameters to "